mirror of
https://github.com/psviderski/uncloud.git
synced 2026-11-10 12:44:16 +00:00
Compare commits
196
Commits
hub-landing
...
nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd93096f1 | ||
|
|
24b4837d08 | ||
|
|
6a64bddf27 | ||
|
|
d1d496cf32 | ||
|
|
286d0e6afb | ||
|
|
cef6916544 | ||
|
|
68d2bd5656 | ||
|
|
99c928f490 | ||
|
|
e81115ef37 | ||
|
|
22b1c68798 | ||
|
|
76b04dd56c | ||
|
|
78facdfcc6 | ||
|
|
7b48ac5a4d | ||
|
|
6c7ddeb82f | ||
|
|
ff6ac45e1e | ||
|
|
c1a7f84dad | ||
|
|
81f30aac26 | ||
|
|
b214d23f21 | ||
|
|
965879b69f | ||
|
|
d66ec2bada | ||
|
|
20e2e97c36 | ||
|
|
e9eab32e6d | ||
|
|
7c6d1be8a7 | ||
|
|
a2987d3ff2 | ||
|
|
3351111feb | ||
|
|
bd695f65a3 | ||
|
|
7e8736fa99 | ||
|
|
8160c9b9d0 | ||
|
|
80d2a323c6 | ||
|
|
5d640b9a57 | ||
|
|
ba224bb5be | ||
|
|
8ddbe3c606 | ||
|
|
ce1c6bd05a | ||
|
|
11e1200fe8 | ||
|
|
0cdf721a6a | ||
|
|
5e26cfbead | ||
|
|
409dc3bfe3 | ||
|
|
5fa236871f | ||
|
|
e3479409b1 | ||
|
|
a1b6b31e9c | ||
|
|
d04772dacd | ||
|
|
87691ec029 | ||
|
|
7ecf63169b | ||
|
|
c58108aa82 | ||
|
|
5d7952952a | ||
|
|
87e99ae369 | ||
|
|
f78a8ca8b5 | ||
|
|
e4bd1ad443 | ||
|
|
0e53f505c0 | ||
|
|
9c21ae6b08 | ||
|
|
70de7b1b8f | ||
|
|
af277639dd | ||
|
|
25ca0a57e8 | ||
|
|
4dae187aa5 | ||
|
|
930e7ea637 | ||
|
|
e50c5fbe57 | ||
|
|
e4455e936a | ||
|
|
ebecf334d7 | ||
|
|
417e402c73 | ||
|
|
9ee3ca15eb | ||
|
|
3482eeefab | ||
|
|
ace8cbf974 | ||
|
|
45d33d87dd | ||
|
|
5c33e4be86 | ||
|
|
88dda435e7 | ||
|
|
ae940c3003 | ||
|
|
e525bdeb7c | ||
|
|
bc09954cbd | ||
|
|
f68c9859d9 | ||
|
|
c65f352250 | ||
|
|
b28c458029 | ||
|
|
3fc1cddf01 | ||
|
|
54a7328a34 | ||
|
|
47a4a9158e | ||
|
|
ad2e1a06d5 | ||
|
|
81d36edc66 | ||
|
|
5fb80e2071 | ||
|
|
d1fe23dec0 | ||
|
|
cec3c75a10 | ||
|
|
751ea97f47 | ||
|
|
abb85f8734 | ||
|
|
37f817aa58 | ||
|
|
db55df64a2 | ||
|
|
e9e7b3a61e | ||
|
|
e81e130544 | ||
|
|
1e471d3ec4 | ||
|
|
f7be7c55c9 | ||
|
|
6ab02f95fe | ||
|
|
9a4098694c | ||
|
|
992b819f5b | ||
|
|
842f796e38 | ||
|
|
0f6c03240b | ||
|
|
c6d0371f15 | ||
|
|
5e01db5e20 | ||
|
|
8aedc2bf19 | ||
|
|
7a88865f64 | ||
|
|
a24fc11caf | ||
|
|
c7e0e36daa | ||
|
|
01426ca03d | ||
|
|
3f86b3de44 | ||
|
|
aaf67687d2 | ||
|
|
1c29d4d5d0 | ||
|
|
00d68d9465 | ||
|
|
43bf2baaf7 | ||
|
|
613cd6e418 | ||
|
|
7a7a313426 | ||
|
|
a5ab6e3788 | ||
|
|
5a956e72de | ||
|
|
ac56754281 | ||
|
|
b7e224a1ef | ||
|
|
351698c280 | ||
|
|
fa77edf53e | ||
|
|
b99abb7c39 | ||
|
|
c2ae11a293 | ||
|
|
f1555259de | ||
|
|
50f8fbcfda | ||
|
|
73f29092ff | ||
|
|
08b24af341 | ||
|
|
b9c54f1ff5 | ||
|
|
4d76dd601c | ||
|
|
3af9936d27 | ||
|
|
dc721e511e | ||
|
|
054b3b8fd4 | ||
|
|
b3896ff642 | ||
|
|
5b2823de1e | ||
|
|
0007f76d09 | ||
|
|
f8e6d1caaa | ||
|
|
e684795aee | ||
|
|
7964273552 | ||
|
|
4ad4bce29d | ||
|
|
d670d18810 | ||
|
|
a1957a156e | ||
|
|
6266e9ec3a | ||
|
|
7c4fcde88d | ||
|
|
d36b28c55a | ||
|
|
6b41340f1d | ||
|
|
4b9ea43402 | ||
|
|
e5f23a9189 | ||
|
|
15e3e32b47 | ||
|
|
a5d73fe4d7 | ||
|
|
e01c8d3cc7 | ||
|
|
925695a44a | ||
|
|
e5b205391e | ||
|
|
e9762dda21 | ||
|
|
5bfdbd5c8f | ||
|
|
1d5c71730f | ||
|
|
ad43569fe5 | ||
|
|
9ea30e6ee9 | ||
|
|
a040ce5f8e | ||
|
|
328d17599b | ||
|
|
83256b5bf7 | ||
|
|
35191c00e0 | ||
|
|
b08cee03e3 | ||
|
|
99e1ae0edb | ||
|
|
5f07bc5832 | ||
|
|
8aa0a96c23 | ||
|
|
8b95db66d1 | ||
|
|
69acf6f58e | ||
|
|
18b4112a33 | ||
|
|
23d6b4409a | ||
|
|
0c42a7df65 | ||
|
|
e3823c29bf | ||
|
|
2f717a10a7 | ||
|
|
7bb3cb9682 | ||
|
|
247154eeb3 | ||
|
|
7bac40ae3e | ||
|
|
d36ff67409 | ||
|
|
3841da9e1d | ||
|
|
cffa03007b | ||
|
|
73d515bb40 | ||
|
|
0c3b8b122f | ||
|
|
2f76187bf5 | ||
|
|
f275a31096 | ||
|
|
e622c06ca6 | ||
|
|
fffc6a8b8a | ||
|
|
cccb8635e1 | ||
|
|
1247f961c2 | ||
|
|
3586a32987 | ||
|
|
24b81af43c | ||
|
|
11df8e4a75 | ||
|
|
9424daff28 | ||
|
|
26951773d1 | ||
|
|
7616f1365a | ||
|
|
2aaaa70b15 | ||
|
|
442b5c62bf | ||
|
|
71da32549a | ||
|
|
9236aa341a | ||
|
|
0dd4bc9d36 | ||
|
|
07acb31bf3 | ||
|
|
a26af3b2ae | ||
|
|
29faeef5a5 | ||
|
|
2f159157cc | ||
|
|
28c88e4e10 | ||
|
|
7f7d091429 | ||
|
|
424263bdd4 | ||
|
|
c95136eae6 |
@@ -2,6 +2,7 @@
|
|||||||
*
|
*
|
||||||
|
|
||||||
# Allow files and directories.
|
# Allow files and directories.
|
||||||
|
!api/
|
||||||
!cmd/
|
!cmd/
|
||||||
!internal/
|
!internal/
|
||||||
!pkg/
|
!pkg/
|
||||||
|
|||||||
@@ -18,3 +18,6 @@ indent_size = 4
|
|||||||
[*.sh]
|
[*.sh]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
[release-notes-*.md]
|
||||||
|
max_line_length = off
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ on:
|
|||||||
- "**.proto"
|
- "**.proto"
|
||||||
- "go.*"
|
- "go.*"
|
||||||
- "Makefile"
|
- "Makefile"
|
||||||
|
- "mise.toml"
|
||||||
- "scripts/**"
|
- "scripts/**"
|
||||||
- "test/**"
|
- "test/**"
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
env:
|
env:
|
||||||
MISE_VERSION: "2026.3.7"
|
MISE_VERSION: "2026.10.22"
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -42,9 +43,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate docs
|
- name: Generate docs
|
||||||
run: |
|
run: |
|
||||||
make cli-docs
|
mise run docs:cli
|
||||||
git diff --exit-code ||
|
git diff --exit-code ||
|
||||||
(echo "Documentation is out of date. Please run 'make cli-docs' and commit the changes." && exit 1)
|
(echo "Documentation is out of date. Please run 'mise run docs:cli' and commit the changes." && exit 1)
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
@@ -70,7 +71,7 @@ jobs:
|
|||||||
version: ${{ env.MISE_VERSION }}
|
version: ${{ env.MISE_VERSION }}
|
||||||
|
|
||||||
# TODO: figure out how to properly lock all platforms in the lockfile and remove this hack.
|
# TODO: figure out how to properly lock all platforms in the lockfile and remove this hack.
|
||||||
# `mise lock` (mise run mise:lock) doesn't generate checksums for some of the tools, e.g. protoc*
|
# `mise lock` doesn't generate checksums for some of the tools, e.g. protoc*
|
||||||
- name: Reset changes in mise.lock after fresh mise install
|
- name: Reset changes in mise.lock after fresh mise install
|
||||||
run: git checkout mise.lock
|
run: git checkout mise.lock
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
env:
|
env:
|
||||||
MISE_VERSION: "2026.3.7"
|
MISE_VERSION: "2026.10.22"
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -30,6 +30,6 @@ jobs:
|
|||||||
run: git checkout mise.lock
|
run: git checkout mise.lock
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: make lint
|
run: mise run lint
|
||||||
|
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MISE_VERSION: "2026.3.7"
|
MISE_VERSION: "2026.10.22"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nightly:
|
nightly:
|
||||||
@@ -57,7 +57,8 @@ jobs:
|
|||||||
|
|
||||||
The commands below show how to install the nightly version of uncloud.
|
The commands below show how to install the nightly version of uncloud.
|
||||||
|
|
||||||
> **Warning**: These are nightly development builds and may be unstable.
|
> [!WARNING]
|
||||||
|
> These are nightly development builds and may be unstable.
|
||||||
|
|
||||||
**Install uncloud CLI:**
|
**Install uncloud CLI:**
|
||||||
|
|
||||||
|
|||||||
@@ -44,3 +44,4 @@ node_modules/
|
|||||||
.devcontainer/
|
.devcontainer/
|
||||||
|
|
||||||
.zed/
|
.zed/
|
||||||
|
research/
|
||||||
|
|||||||
+19
-10
@@ -20,9 +20,9 @@ snapshot:
|
|||||||
version_template: "{{ incminor .Version }}-nightly-{{.ShortCommit}}"
|
version_template: "{{ incminor .Version }}-nightly-{{.ShortCommit}}"
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: uncloud
|
- id: uc
|
||||||
main: ./cmd/uncloud
|
main: ./cmd/uc
|
||||||
binary: uncloud
|
binary: uc
|
||||||
env:
|
env:
|
||||||
# Use CGO to be able to build fsevent required by compose on macOS.
|
# Use CGO to be able to build fsevent required by compose on macOS.
|
||||||
- CGO_ENABLED={{ if eq .Os "darwin" }}1{{ else }}0{{ end }}
|
- CGO_ENABLED={{ if eq .Os "darwin" }}1{{ else }}0{{ end }}
|
||||||
@@ -34,7 +34,12 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
ldflags:
|
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
|
- id: uncloudd
|
||||||
main: ./cmd/uncloudd
|
main: ./cmd/uncloudd
|
||||||
@@ -47,12 +52,17 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
ldflags:
|
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:
|
archives:
|
||||||
- id: uncloud
|
- id: uc
|
||||||
builds:
|
builds:
|
||||||
- uncloud
|
- uc
|
||||||
formats:
|
formats:
|
||||||
# gz is not compatible with homebrew releases, so we use tar.gz
|
# gz is not compatible with homebrew releases, so we use tar.gz
|
||||||
- tar.gz
|
- tar.gz
|
||||||
@@ -93,10 +103,9 @@ brews:
|
|||||||
description: "Uncloud CLI"
|
description: "Uncloud CLI"
|
||||||
homepage: "https://uncloud.run"
|
homepage: "https://uncloud.run"
|
||||||
ids:
|
ids:
|
||||||
- uncloud
|
- uc
|
||||||
install: |
|
install: |
|
||||||
bin.install "uncloud"
|
bin.install "uc"
|
||||||
bin.install_symlink "uncloud" => "uc"
|
|
||||||
skip_upload: false
|
skip_upload: false
|
||||||
repository:
|
repository:
|
||||||
owner: psviderski
|
owner: psviderski
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
packages:
|
packages:
|
||||||
|
github.com/docker/docker/client:
|
||||||
|
config:
|
||||||
|
dir: internal/machine/docker
|
||||||
|
pkgname: docker
|
||||||
|
interfaces:
|
||||||
|
APIClient:
|
||||||
github.com/psviderski/uncloud/internal/machine/caddyconfig:
|
github.com/psviderski/uncloud/internal/machine/caddyconfig:
|
||||||
interfaces:
|
interfaces:
|
||||||
CaddyfileValidator:
|
CaddyfileValidator:
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ management.
|
|||||||
|
|
||||||
- **`cmd/`**: Contains main applications
|
- **`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
|
- `uncloudd/`: Daemon that runs on each machine
|
||||||
- `ucind/`: Development cluster management for testing
|
- `ucind/`: Development cluster management for testing
|
||||||
|
|
||||||
@@ -98,53 +98,28 @@ management.
|
|||||||
|
|
||||||
- **`misc/`**: Design documents and guides
|
- **`misc/`**: Design documents and guides
|
||||||
|
|
||||||
## Key Technologies
|
|
||||||
|
|
||||||
### Core Dependencies
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Networking and orchestration
|
|
||||||
github.com/docker/docker // Docker API client
|
|
||||||
github.com/docker/compose/v2 // Docker Compose integration
|
|
||||||
golang.zx2c4.com/wireguard // WireGuard implementation
|
|
||||||
github.com/hashicorp/serf // Gossip protocol
|
|
||||||
|
|
||||||
// State management
|
|
||||||
github.com/ipfs/go-ds-crdt // CRDT distributed storage
|
|
||||||
github.com/dgraph-io/badger/v3 // Embedded database
|
|
||||||
|
|
||||||
// Web proxy
|
|
||||||
github.com/caddyserver/caddy/v2 // HTTP server and reverse proxy
|
|
||||||
|
|
||||||
// CLI and UX
|
|
||||||
github.com/spf13/cobra // CLI framework
|
|
||||||
github.com/charmbracelet/huh // Interactive forms
|
|
||||||
|
|
||||||
// gRPC and networking
|
|
||||||
google.golang.org/grpc // gRPC framework
|
|
||||||
github.com/siderolabs/grpc-proxy // gRPC proxy for forwarding
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
|
|
||||||
### Build and Development
|
### Build and Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build binaries
|
# Build binaries
|
||||||
go build -o uncloud ./cmd/uncloud
|
go build -o uc ./cmd/uc
|
||||||
go build -o uncloudd ./cmd/uncloudd
|
go build -o uncloudd ./cmd/uncloudd
|
||||||
```
|
```
|
||||||
|
|
||||||
### Key Make Targets
|
### Formatting and Linting
|
||||||
|
|
||||||
|
- `mise run lint`: Check Go code and formatting without changing files
|
||||||
|
- `mise run lint:fix`: Apply formatting and available lint fixes
|
||||||
|
|
||||||
|
### Other Development Tasks
|
||||||
|
|
||||||
- `proto`: Generate protobuf code
|
- `proto`: Generate protobuf code
|
||||||
- `ucind-cluster`: Create development cluster
|
- `ucind-cluster`: Create development cluster
|
||||||
- `update-dev`: Deploy to development machines
|
- `update-dev`: Deploy to development machines
|
||||||
- `demo-reset`: Reset demo environment
|
- `demo-reset`: Reset demo environment
|
||||||
- `fmt`: Format code
|
|
||||||
- `test`: Run all tests
|
- `test`: Run all tests
|
||||||
- `lint`: Lint the code using golangci-lint
|
|
||||||
- `lint-and-fix`: Lint the code and fix issues whenever possible
|
|
||||||
|
|
||||||
## CLI Commands Structure
|
## CLI Commands Structure
|
||||||
|
|
||||||
@@ -188,7 +163,7 @@ uc context use <name> # Switch context
|
|||||||
- **Package naming**: Use clear, descriptive names
|
- **Package naming**: Use clear, descriptive names
|
||||||
- **Error handling**: Wrap errors with context using `fmt.Errorf`
|
- **Error handling**: Wrap errors with context using `fmt.Errorf`
|
||||||
- **Logging**: Use structured logging with levels
|
- **Logging**: Use structured logging with levels
|
||||||
- **gRPC**: Services defined in `internal/machine/api/pb/`
|
- **gRPC**: Services defined in `api/pb/`
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
@@ -196,7 +171,6 @@ uc context use <name> # Switch context
|
|||||||
- Unit tests alongside source files (`*_test.go`)
|
- Unit tests alongside source files (`*_test.go`)
|
||||||
- Integration tests in `test/e2e/`
|
- Integration tests in `test/e2e/`
|
||||||
- Test fixtures in `test/fixtures/`
|
- Test fixtures in `test/fixtures/`
|
||||||
- Use table driven tests whenever possible
|
|
||||||
- Use the `testify` library for assertions (e.g., `require.Equal`, `assert.Nil`)
|
- Use the `testify` library for assertions (e.g., `require.Equal`, `assert.Nil`)
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
@@ -239,7 +213,7 @@ uc context use <name> # Switch context
|
|||||||
|
|
||||||
### Important Files to Understand
|
### 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/cli/cli.go`: CLI implementation and configuration
|
||||||
- `internal/machine/machine.go`: Core machine management
|
- `internal/machine/machine.go`: Core machine management
|
||||||
- `pkg/api/`: Public API definitions
|
- `pkg/api/`: Public API definitions
|
||||||
|
|||||||
+9
-5
@@ -18,6 +18,7 @@ RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o uncloudd ./cmd/uncloudd
|
|||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION} AS corrosion-download
|
FROM alpine:${ALPINE_VERSION} AS corrosion-download
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG CORROSION_VERSION
|
||||||
|
|
||||||
RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
|
RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
|
||||||
"amd64") echo "x86_64" ;; \
|
"amd64") echo "x86_64" ;; \
|
||||||
@@ -25,20 +26,21 @@ RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
|
|||||||
*) echo "Architecture '${TARGETARCH}' not supported" >&2 && exit 1 ;; \
|
*) echo "Architecture '${TARGETARCH}' not supported" >&2 && exit 1 ;; \
|
||||||
esac) \
|
esac) \
|
||||||
&& wget -q -O /tmp/corrosion.tar.gz \
|
&& wget -q -O /tmp/corrosion.tar.gz \
|
||||||
"https://github.com/psviderski/corrosion/releases/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 \
|
&& 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
|
&& rm /tmp/corrosion.tar.gz /tmp/corrosion
|
||||||
|
|
||||||
# Beware that more modern images like chainguard/wolfi-base build glibc with flags that require newer CPU features,
|
# Beware that more modern images like chainguard/wolfi-base build glibc with flags that require newer CPU features,
|
||||||
# e.g. x86-64-v2. So such image may fail with "Fatal glibc error: CPU does not support x86-64-v2" on older CPUs.
|
# e.g. x86-64-v2. So such image may fail with "Fatal glibc error: CPU does not support x86-64-v2" on older CPUs.
|
||||||
FROM gcr.io/distroless/cc-debian12:latest AS corrosion
|
FROM gcr.io/distroless/cc-debian13:latest AS corrosion
|
||||||
COPY --from=corrosion-download /usr/local/bin/corrosion /usr/local/bin/corrosion
|
COPY --from=corrosion-download /usr/bin/corrosion /usr/bin/corrosion
|
||||||
CMD ["corrosion", "agent"]
|
CMD ["corrosion", "agent"]
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION} AS corrosion-image-tarball
|
FROM alpine:${ALPINE_VERSION} AS corrosion-image-tarball
|
||||||
ARG CORROSION_IMAGE="ghcr.io/psviderski/corrosion:latest"
|
ARG CORROSION_VERSION
|
||||||
|
ARG CORROSION_IMAGE="ghcr.io/unlabs-dev/corrosion:${CORROSION_VERSION}"
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
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.
|
# Uncloud-in-Docker (ucind) image for running Uncloud test clusters using Docker.
|
||||||
FROM docker:29.4.0-dind AS ucind
|
FROM docker:29.4.0-dind AS ucind
|
||||||
|
ARG CORROSION_VERSION
|
||||||
|
|
||||||
# Create system group and user 'uncloud'.
|
# Create system group and user 'uncloud'.
|
||||||
RUN addgroup -S uncloud && adduser -SHD -h /nonexistent -G uncloud -g "" uncloud
|
RUN addgroup -S uncloud && adduser -SHD -h /nonexistent -G uncloud -g "" uncloud
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
|
|||||||
+36
-7
@@ -32,13 +32,14 @@ available outside the project, so they don't interfere with system packages.
|
|||||||
Build the CLI:
|
Build the CLI:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go build -o uc ./cmd/uncloud
|
go build -o uc ./cmd/uc
|
||||||
```
|
```
|
||||||
|
|
||||||
Or build and run the CLI with a single command:
|
Or build and run the CLI with mise. It forwards arguments, including help flags, directly to the CLI:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go run ./cmd/uncloud --help
|
mise uc --help
|
||||||
|
mise uc image rm -h
|
||||||
```
|
```
|
||||||
|
|
||||||
The Uncloud daemon (`uncloudd`) only supports Linux, so you need to cross-compile it if you're developing on macOS or
|
The Uncloud daemon (`uncloudd`) only supports Linux, so you need to cross-compile it if you're developing on macOS or
|
||||||
@@ -99,15 +100,21 @@ mise run dev:reset user@host1 user@host2
|
|||||||
|
|
||||||
## Linting and formatting
|
## Linting and formatting
|
||||||
|
|
||||||
Lint and format the code:
|
Check the code and formatting without changing files:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
make lint-and-fix
|
mise run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply formatting and available lint fixes:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mise run lint:fix
|
||||||
```
|
```
|
||||||
|
|
||||||
## Code generation
|
## 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
|
```shell
|
||||||
mise run proto
|
mise run proto
|
||||||
@@ -116,9 +123,31 @@ mise run proto
|
|||||||
Regenerate mocks:
|
Regenerate mocks:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
make mocks
|
mise run mocks
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Regenerate the CLI reference docs after changing commands or flags:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mise run docs:cli
|
||||||
|
```
|
||||||
|
|
||||||
|
To preview the documentation website, install its dependencies from the project root:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm ci --prefix website
|
||||||
|
```
|
||||||
|
|
||||||
|
Then start the local development server:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mise run docs:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The server opens the site (http://localhost:3000/) in your browser and reloads it as you edit files in `website/`.
|
||||||
|
|
||||||
## Uncloud in Docker (UCinD)
|
## Uncloud in Docker (UCinD)
|
||||||
|
|
||||||
The [`ucind`](./cmd/ucind) CLI lets you run Uncloud clusters locally using Docker containers instead of real machines.
|
The [`ucind`](./cmd/ucind) CLI lets you run Uncloud clusters locally using Docker containers instead of real machines.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# TODO: Makefile is deprecated, add new targets as mise tasks in mise.toml instead.
|
# TODO: Makefile is deprecated, add new targets as mise tasks in mise.toml instead.
|
||||||
|
|
||||||
CORROSION_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
|
UCIND_IMAGE ?= ghcr.io/psviderski/ucind:latest
|
||||||
|
|
||||||
.PHONY: ucind-cluster
|
.PHONY: ucind-cluster
|
||||||
@@ -9,20 +10,17 @@ ucind-cluster:
|
|||||||
|
|
||||||
.PHONY: corrosion-image
|
.PHONY: corrosion-image
|
||||||
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
|
.PHONY: corrosion-multiarch-image-push
|
||||||
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
|
.PHONY: ucind-multiarch-image-push
|
||||||
ucind-multiarch-image-push:
|
ucind-multiarch-image-push:
|
||||||
docker buildx build --push --platform linux/amd64,linux/arm64 -t "$(UCIND_IMAGE)" --target ucind .
|
docker buildx build --push --platform linux/amd64,linux/arm64 -t "$(UCIND_IMAGE)" --target ucind .
|
||||||
|
|
||||||
.PHONY: mocks
|
|
||||||
mocks:
|
|
||||||
@mockery
|
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
ifeq ($(TEST_NAME),)
|
ifeq ($(TEST_NAME),)
|
||||||
@@ -49,25 +47,3 @@ test-clean:
|
|||||||
else \
|
else \
|
||||||
echo "Nothing to clean."; \
|
echo "Nothing to clean."; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
.PHONY: vet
|
|
||||||
vet:
|
|
||||||
go vet ./...
|
|
||||||
|
|
||||||
.PHONY: format fmt
|
|
||||||
format fmt:
|
|
||||||
GOOS=linux golangci-lint fmt
|
|
||||||
|
|
||||||
LINT_TARGETS := lint lint-and-fix
|
|
||||||
.PHONY: $(LINT_TARGETS) _lint
|
|
||||||
$(LINT_TARGETS): _lint
|
|
||||||
lint: ARGS=
|
|
||||||
lint-and-fix: ARGS=--fix
|
|
||||||
_lint:
|
|
||||||
# Explicitly set OS to Linux to not skip *_linux.go files when running on macOS.
|
|
||||||
# Uncloud daemon won't likely support OS other than Linux anytime soon, so for now we can rely on that.
|
|
||||||
GOOS=linux golangci-lint run $(ARGS)
|
|
||||||
|
|
||||||
.PHONY: cli-docs
|
|
||||||
cli-docs:
|
|
||||||
go run ./cmd/uncloud docs
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ complexity of Kubernetes.
|
|||||||
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
||||||
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
||||||
Encrypt.
|
Encrypt.
|
||||||
|
* **Cluster storage for TLS certificates**: [Cluster storage](https://github.com/unlabs-dev/caddy-uncloud) for Caddy
|
||||||
|
shares certificates and ACME challenge tokens across machines and coordinates issuance.
|
||||||
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
||||||
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
||||||
|
|
||||||
@@ -154,7 +156,6 @@ Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
|||||||
```bash
|
```bash
|
||||||
$ uc machine init --name oracle-vm ubuntu@152.67.101.197
|
$ uc machine init --name oracle-vm ubuntu@152.67.101.197
|
||||||
|
|
||||||
Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh
|
|
||||||
⏳ Running Uncloud install script...
|
⏳ Running Uncloud install script...
|
||||||
✓ Docker is already installed.
|
✓ Docker is already installed.
|
||||||
⏳ Installing Docker...
|
⏳ Installing Docker...
|
||||||
@@ -167,11 +168,10 @@ Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski
|
|||||||
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
||||||
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
||||||
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
||||||
|
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
||||||
|
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
|
||||||
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
||||||
⏳ Downloading uncloud-corrosion binary: https://github.com/psviderski/corrosion/releases/latest/download/corrosion-aarch64-unknown-linux-gnu.tar.gz
|
|
||||||
✓ uncloud-corrosion binary installed: /usr/local/bin/uncloud-corrosion
|
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud-corrosion.service
|
|
||||||
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
||||||
✓ Uncloud machine daemon started.
|
✓ Uncloud machine daemon started.
|
||||||
✓ Uncloud installed on the machine successfully! 🎉
|
✓ Uncloud installed on the machine successfully! 🎉
|
||||||
@@ -190,21 +190,20 @@ DNS records updated to use only the internet-reachable machines running caddy se
|
|||||||
*.xuw3xd.cluster.uncloud.run A → 152.67.101.197
|
*.xuw3xd.cluster.uncloud.run A → 152.67.101.197
|
||||||
```
|
```
|
||||||
|
|
||||||
1. The CLI SSHs into the machine and installs Docker, the `uncloudd` machine daemon and
|
1. The CLI SSHs into the machine and installs Docker and the `uncloudd` machine daemon, managed by systemd.
|
||||||
[corrosion](https://github.com/superfly/corrosion) service, managed by systemd.
|
|
||||||
2. Generates a unique WireGuard key pair, allocates a dedicated subnet `10.210.0.0/24` for the machine and its
|
2. Generates a unique WireGuard key pair, allocates a dedicated subnet `10.210.0.0/24` for the machine and its
|
||||||
containers, and configures `uncloudd` accordingly. All subsequent communication happens with `uncloudd`
|
containers, and configures `uncloudd` accordingly. All subsequent communication happens with `uncloudd`
|
||||||
through its gRPC API over SSH.
|
through its gRPC API over SSH.
|
||||||
3. Configures and starts `corrosion`, 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.
|
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
|
5. This machine becomes an entry point for the newly created cluster. Its address is stored as a connection info in the
|
||||||
`~/.config/uncloud` on your local machine.
|
cluster config at `~/.config/uncloud/config.yaml` on your local machine.
|
||||||
|
|
||||||
**When you add another machine:**
|
**When you add another machine:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uc machine add --name hetzner-server root@5.223.45.199
|
$ uc machine add --name hetzner-server root@5.223.45.199
|
||||||
Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh
|
|
||||||
⏳ Running Uncloud install script...
|
⏳ Running Uncloud install script...
|
||||||
✓ Docker is already installed.
|
✓ Docker is already installed.
|
||||||
✓ Linux user and group 'uncloud' created.
|
✓ Linux user and group 'uncloud' created.
|
||||||
@@ -213,11 +212,10 @@ Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski
|
|||||||
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
||||||
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
||||||
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
||||||
|
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
||||||
|
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
|
||||||
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
||||||
⏳ Downloading uncloud-corrosion binary: https://github.com/psviderski/corrosion/releases/latest/download/corrosion-x86_64-unknown-linux-gnu.tar.gz
|
|
||||||
✓ uncloud-corrosion binary installed: /usr/local/bin/uncloud-corrosion
|
|
||||||
✓ Systemd unit file created: /etc/systemd/system/uncloud-corrosion.service
|
|
||||||
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
||||||
✓ Uncloud machine daemon started.
|
✓ Uncloud machine daemon started.
|
||||||
✓ Uncloud installed on the machine successfully! 🎉
|
✓ Uncloud installed on the machine successfully! 🎉
|
||||||
@@ -246,9 +244,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.
|
3. Registers the second machine in the cluster state and exchanges WireGuard keys with the first machine.
|
||||||
4. Both machines establish a WireGuard tunnel between each other, allowing Docker containers connected to the bridge
|
4. Both machines establish a WireGuard tunnel between each other, allowing Docker containers connected to the bridge
|
||||||
network to communicate directly across machines.
|
network to communicate directly across machines.
|
||||||
5. Configures and starts `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.
|
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
|
If one more machine is added, the process repeats with a new subnet. The new machine needs to establish a WireGuard
|
||||||
connection with only one of the existing machines. Other machines will learn about it through the shared cluster state
|
connection with only one of the existing machines. Other machines will learn about it through the shared cluster state
|
||||||
@@ -326,9 +325,9 @@ I'm grateful to the following projects that inspired Uncloud's design and implem
|
|||||||
* [Tailscale](https://tailscale.com/) — for pioneering the vision of decentralised flat mesh networking with an amazing
|
* [Tailscale](https://tailscale.com/) — for pioneering the vision of decentralised flat mesh networking with an amazing
|
||||||
user experience that feels like magic.
|
user experience that feels like magic.
|
||||||
* [Talos Linux](https://github.com/siderolabs/talos)
|
* [Talos Linux](https://github.com/siderolabs/talos)
|
||||||
and [KubeSpan](https://www.talos.dev/v1.10/talos-guides/network/kubespan/) — for the machine API design using
|
and [KubeSpan](https://www.talos.dev/v1.10/talos-guides/network/kubespan/) — for inspiring Uncloud's API routing
|
||||||
[grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for its elegant approach to secure WireGuard-based overlay
|
design with [grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for their elegant approach to secure
|
||||||
networking with zero configuration.
|
WireGuard-based overlay networking with zero configuration.
|
||||||
* [Docker Swarm Classic](https://github.com/docker-archive/classicswarm) and
|
* [Docker Swarm Classic](https://github.com/docker-archive/classicswarm) and
|
||||||
[Rancher 1.x](http://rancher-com-website-main-elb-elb-1798790864.us-west-2.elb.amazonaws.com/docs/rancher/v1.6/en/)
|
[Rancher 1.x](http://rancher-com-website-main-elb-elb-1798790864.us-west-2.elb.amazonaws.com/docs/rancher/v1.6/en/)
|
||||||
— for showing the power of simplicity and pragmatism in container orchestration and that not every problem needs the
|
— for showing the power of simplicity and pragmatism in container orchestration and that not every problem needs the
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.34.2
|
// protoc-gen-go v1.34.2
|
||||||
// protoc v5.27.3
|
// protoc v5.27.3
|
||||||
// source: internal/machine/api/pb/caddy.proto
|
// source: api/pb/caddy.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -27,16 +27,18 @@ type GetCaddyConfigResponse struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// The generated Caddyfile content.
|
// The saved Caddyfile content.
|
||||||
Caddyfile string `protobuf:"bytes,1,opt,name=caddyfile,proto3" json:"caddyfile,omitempty"`
|
Caddyfile string `protobuf:"bytes,1,opt,name=caddyfile,proto3" json:"caddyfile,omitempty"`
|
||||||
// Timestamp when the config was last modified.
|
// Timestamp when the config was last modified.
|
||||||
ModifiedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=modified_at,json=modifiedAt,proto3" json:"modified_at,omitempty"`
|
ModifiedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=modified_at,json=modifiedAt,proto3" json:"modified_at,omitempty"`
|
||||||
|
// Error from the latest unsuccessful reconciliation attempt.
|
||||||
|
LastReconciliationError string `protobuf:"bytes,3,opt,name=last_reconciliation_error,json=lastReconciliationError,proto3" json:"last_reconciliation_error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetCaddyConfigResponse) Reset() {
|
func (x *GetCaddyConfigResponse) Reset() {
|
||||||
*x = GetCaddyConfigResponse{}
|
*x = GetCaddyConfigResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_caddy_proto_msgTypes[0]
|
mi := &file_api_pb_caddy_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -49,7 +51,7 @@ func (x *GetCaddyConfigResponse) String() string {
|
|||||||
func (*GetCaddyConfigResponse) ProtoMessage() {}
|
func (*GetCaddyConfigResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetCaddyConfigResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetCaddyConfigResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_caddy_proto_msgTypes[0]
|
mi := &file_api_pb_caddy_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -62,7 +64,7 @@ func (x *GetCaddyConfigResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetCaddyConfigResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetCaddyConfigResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetCaddyConfigResponse) Descriptor() ([]byte, []int) {
|
func (*GetCaddyConfigResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_caddy_proto_rawDescGZIP(), []int{0}
|
return file_api_pb_caddy_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetCaddyConfigResponse) GetCaddyfile() string {
|
func (x *GetCaddyConfigResponse) GetCaddyfile() string {
|
||||||
@@ -79,53 +81,62 @@ func (x *GetCaddyConfigResponse) GetModifiedAt() *timestamppb.Timestamp {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var File_internal_machine_api_pb_caddy_proto protoreflect.FileDescriptor
|
func (x *GetCaddyConfigResponse) GetLastReconciliationError() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.LastReconciliationError
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
var file_internal_machine_api_pb_caddy_proto_rawDesc = []byte{
|
var File_api_pb_caddy_proto protoreflect.FileDescriptor
|
||||||
0x0a, 0x23, 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, 0x61, 0x64, 0x64, 0x79, 0x2e,
|
var file_api_pb_caddy_proto_rawDesc = []byte{
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67,
|
0x0a, 0x12, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x70,
|
||||||
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74,
|
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
|
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||||
0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x73, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||||
|
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaf, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43,
|
||||||
0x61, 0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
0x70, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3a, 0x0a,
|
||||||
0x05, 0x43, 0x61, 0x64, 0x64, 0x79, 0x12, 0x40, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
|
0x19, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x69, 0x61,
|
||||||
0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x61, 0x70,
|
0x52, 0x17, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x69, 0x61,
|
||||||
0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x32, 0x49, 0x0a, 0x05, 0x43, 0x61, 0x64,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68,
|
0x64, 0x79, 0x12, 0x40, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b,
|
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65,
|
||||||
0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70,
|
0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
file_internal_machine_api_pb_caddy_proto_rawDescOnce sync.Once
|
file_api_pb_caddy_proto_rawDescOnce sync.Once
|
||||||
file_internal_machine_api_pb_caddy_proto_rawDescData = file_internal_machine_api_pb_caddy_proto_rawDesc
|
file_api_pb_caddy_proto_rawDescData = file_api_pb_caddy_proto_rawDesc
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_internal_machine_api_pb_caddy_proto_rawDescGZIP() []byte {
|
func file_api_pb_caddy_proto_rawDescGZIP() []byte {
|
||||||
file_internal_machine_api_pb_caddy_proto_rawDescOnce.Do(func() {
|
file_api_pb_caddy_proto_rawDescOnce.Do(func() {
|
||||||
file_internal_machine_api_pb_caddy_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_machine_api_pb_caddy_proto_rawDescData)
|
file_api_pb_caddy_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_caddy_proto_rawDescData)
|
||||||
})
|
})
|
||||||
return file_internal_machine_api_pb_caddy_proto_rawDescData
|
return file_api_pb_caddy_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_internal_machine_api_pb_caddy_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_api_pb_caddy_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
var file_internal_machine_api_pb_caddy_proto_goTypes = []any{
|
var file_api_pb_caddy_proto_goTypes = []any{
|
||||||
(*GetCaddyConfigResponse)(nil), // 0: api.GetCaddyConfigResponse
|
(*GetCaddyConfigResponse)(nil), // 0: api.GetCaddyConfigResponse
|
||||||
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
|
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
|
||||||
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
|
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
|
||||||
}
|
}
|
||||||
var file_internal_machine_api_pb_caddy_proto_depIdxs = []int32{
|
var file_api_pb_caddy_proto_depIdxs = []int32{
|
||||||
1, // 0: api.GetCaddyConfigResponse.modified_at:type_name -> google.protobuf.Timestamp
|
1, // 0: api.GetCaddyConfigResponse.modified_at:type_name -> google.protobuf.Timestamp
|
||||||
2, // 1: api.Caddy.GetConfig:input_type -> google.protobuf.Empty
|
2, // 1: api.Caddy.GetConfig:input_type -> google.protobuf.Empty
|
||||||
0, // 2: api.Caddy.GetConfig:output_type -> api.GetCaddyConfigResponse
|
0, // 2: api.Caddy.GetConfig:output_type -> api.GetCaddyConfigResponse
|
||||||
@@ -136,13 +147,13 @@ var file_internal_machine_api_pb_caddy_proto_depIdxs = []int32{
|
|||||||
0, // [0:1] is the sub-list for field type_name
|
0, // [0:1] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_internal_machine_api_pb_caddy_proto_init() }
|
func init() { file_api_pb_caddy_proto_init() }
|
||||||
func file_internal_machine_api_pb_caddy_proto_init() {
|
func file_api_pb_caddy_proto_init() {
|
||||||
if File_internal_machine_api_pb_caddy_proto != nil {
|
if File_api_pb_caddy_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
if !protoimpl.UnsafeEnabled {
|
||||||
file_internal_machine_api_pb_caddy_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
file_api_pb_caddy_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*GetCaddyConfigResponse); i {
|
switch v := v.(*GetCaddyConfigResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -159,18 +170,18 @@ func file_internal_machine_api_pb_caddy_proto_init() {
|
|||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_internal_machine_api_pb_caddy_proto_rawDesc,
|
RawDescriptor: file_api_pb_caddy_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 1,
|
NumMessages: 1,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
GoTypes: file_internal_machine_api_pb_caddy_proto_goTypes,
|
GoTypes: file_api_pb_caddy_proto_goTypes,
|
||||||
DependencyIndexes: file_internal_machine_api_pb_caddy_proto_depIdxs,
|
DependencyIndexes: file_api_pb_caddy_proto_depIdxs,
|
||||||
MessageInfos: file_internal_machine_api_pb_caddy_proto_msgTypes,
|
MessageInfos: file_api_pb_caddy_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_internal_machine_api_pb_caddy_proto = out.File
|
File_api_pb_caddy_proto = out.File
|
||||||
file_internal_machine_api_pb_caddy_proto_rawDesc = nil
|
file_api_pb_caddy_proto_rawDesc = nil
|
||||||
file_internal_machine_api_pb_caddy_proto_goTypes = nil
|
file_api_pb_caddy_proto_goTypes = nil
|
||||||
file_internal_machine_api_pb_caddy_proto_depIdxs = nil
|
file_api_pb_caddy_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package api;
|
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/empty.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
@@ -13,8 +13,10 @@ service Caddy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message GetCaddyConfigResponse {
|
message GetCaddyConfigResponse {
|
||||||
// The generated Caddyfile content.
|
// The saved Caddyfile content.
|
||||||
string caddyfile = 1;
|
string caddyfile = 1;
|
||||||
// Timestamp when the config was last modified.
|
// Timestamp when the config was last modified.
|
||||||
google.protobuf.Timestamp modified_at = 2;
|
google.protobuf.Timestamp modified_at = 2;
|
||||||
}
|
// Error from the latest unsuccessful reconciliation attempt.
|
||||||
|
string last_reconciliation_error = 3;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
// - protoc v5.27.3
|
// - protoc v5.27.3
|
||||||
// source: internal/machine/api/pb/caddy.proto
|
// source: api/pb/caddy.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -120,5 +120,5 @@ var Caddy_ServiceDesc = grpc.ServiceDesc{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "internal/machine/api/pb/caddy.proto",
|
Metadata: "api/pb/caddy.proto",
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,848 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.34.2
|
||||||
|
// protoc v5.27.3
|
||||||
|
// source: api/pb/caddy_storage.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type StoreCaddyStorageRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StoreCaddyStorageRequest) Reset() {
|
||||||
|
*x = StoreCaddyStorageRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StoreCaddyStorageRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StoreCaddyStorageRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StoreCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StoreCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StoreCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StoreCaddyStorageRequest) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StoreCaddyStorageRequest) GetValue() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadCaddyStorageRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageRequest) Reset() {
|
||||||
|
*x = LoadCaddyStorageRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LoadCaddyStorageRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LoadCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LoadCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageRequest) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadCaddyStorageResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||||
|
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageResponse) Reset() {
|
||||||
|
*x = LoadCaddyStorageResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LoadCaddyStorageResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LoadCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LoadCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageResponse) GetValue() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoadCaddyStorageResponse) GetUpdatedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.UpdatedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteCaddyStorageRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCaddyStorageRequest) Reset() {
|
||||||
|
*x = DeleteCaddyStorageRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCaddyStorageRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteCaddyStorageRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeleteCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteCaddyStorageRequest) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListCaddyStorageRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||||
|
Recursive bool `protobuf:"varint,2,opt,name=recursive,proto3" json:"recursive,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageRequest) Reset() {
|
||||||
|
*x = ListCaddyStorageRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListCaddyStorageRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageRequest) GetPrefix() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Prefix
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageRequest) GetRecursive() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Recursive
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListCaddyStorageResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Keys []string `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageResponse) Reset() {
|
||||||
|
*x = ListCaddyStorageResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListCaddyStorageResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[5]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCaddyStorageResponse) GetKeys() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Keys
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatCaddyStorageRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageRequest) Reset() {
|
||||||
|
*x = StatCaddyStorageRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StatCaddyStorageRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[6]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StatCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StatCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageRequest) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatCaddyStorageResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
||||||
|
Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
|
||||||
|
IsTerminal bool `protobuf:"varint,4,opt,name=is_terminal,json=isTerminal,proto3" json:"is_terminal,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageResponse) Reset() {
|
||||||
|
*x = StatCaddyStorageResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StatCaddyStorageResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[7]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StatCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StatCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageResponse) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageResponse) GetUpdatedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.UpdatedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageResponse) GetSize() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Size
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StatCaddyStorageResponse) GetIsTerminal() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsTerminal
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListCertificatesResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Certificates []*IssuedCertificate `protobuf:"bytes,1,rep,name=certificates,proto3" json:"certificates,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCertificatesResponse) Reset() {
|
||||||
|
*x = ListCertificatesResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[8]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCertificatesResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListCertificatesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListCertificatesResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[8]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListCertificatesResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListCertificatesResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{8}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListCertificatesResponse) GetCertificates() []*IssuedCertificate {
|
||||||
|
if x != nil {
|
||||||
|
return x.Certificates
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IssuedCertificate struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Subject Alternative Name (SAN) of the certificate. Caddy doesn't issue certificates with multiple SANs.
|
||||||
|
San string `protobuf:"bytes,1,opt,name=san,proto3" json:"san,omitempty"`
|
||||||
|
// The PEM-encoding of DER-encoded ASN.1 data for the cert or chain, leaf first.
|
||||||
|
Chain []byte `protobuf:"bytes,2,opt,name=chain,proto3" json:"chain,omitempty"`
|
||||||
|
// Any extra information associated with the certificate, usually provided by the issuer implementation.
|
||||||
|
// JSON-encoded, may be absent or null.
|
||||||
|
IssuerData []byte `protobuf:"bytes,3,opt,name=issuer_data,json=issuerData,proto3" json:"issuer_data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IssuedCertificate) Reset() {
|
||||||
|
*x = IssuedCertificate{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[9]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IssuedCertificate) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*IssuedCertificate) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *IssuedCertificate) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_caddy_storage_proto_msgTypes[9]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use IssuedCertificate.ProtoReflect.Descriptor instead.
|
||||||
|
func (*IssuedCertificate) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{9}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IssuedCertificate) GetSan() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.San
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IssuedCertificate) GetChain() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Chain
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IssuedCertificate) GetIssuerData() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.IssuerData
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_api_pb_caddy_storage_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_api_pb_caddy_storage_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x1a, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x61, 0x64, 0x64, 0x79, 0x5f, 0x73,
|
||||||
|
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70,
|
||||||
|
0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||||
|
0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f,
|
||||||
|
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
|
||||||
|
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||||
|
0x42, 0x0a, 0x18, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
||||||
|
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||||
|
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
||||||
|
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
|
||||||
|
0x6c, 0x75, 0x65, 0x22, 0x2b, 0x0a, 0x17, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79,
|
||||||
|
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10,
|
||||||
|
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||||
|
0x22, 0x6b, 0x0a, 0x18, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
||||||
|
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||||
|
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||||
|
0x75, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
|
||||||
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||||
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||||
|
0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x2d, 0x0a,
|
||||||
|
0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72,
|
||||||
|
0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
|
||||||
|
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4f, 0x0a, 0x17,
|
||||||
|
0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69,
|
||||||
|
0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12,
|
||||||
|
0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
|
0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x22, 0x2e, 0x0a,
|
||||||
|
0x18, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
||||||
|
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79,
|
||||||
|
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x2b, 0x0a,
|
||||||
|
0x17, 0x53, 0x74, 0x61, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
||||||
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x9c, 0x01, 0x0a, 0x18, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64,
|
||||||
|
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||||
|
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||||
|
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74,
|
||||||
|
0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||||
|
0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x74,
|
||||||
|
0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69,
|
||||||
|
0x73, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0x56, 0x0a, 0x18, 0x4c, 0x69, 0x73,
|
||||||
|
0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||||
|
0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70,
|
||||||
|
0x69, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||||
|
0x61, 0x74, 0x65, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
|
||||||
|
0x73, 0x22, 0x5c, 0x0a, 0x11, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69,
|
||||||
|
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x61, 0x6e, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x61, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69,
|
||||||
|
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1f,
|
||||||
|
0x0a, 0x0b, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20,
|
||||||
|
0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x32,
|
||||||
|
0xaa, 0x03, 0x0a, 0x0c, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
||||||
|
0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
|
0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
||||||
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
|
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
|
0x12, 0x43, 0x0a, 0x04, 0x4c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
|
||||||
|
0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x61,
|
||||||
|
0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
|
||||||
|
0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x64, 0x64,
|
||||||
|
0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
|
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
|
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12,
|
||||||
|
0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53,
|
||||||
|
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e,
|
||||||
|
0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
||||||
|
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x04,
|
||||||
|
0x53, 0x74, 0x61, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x43,
|
||||||
|
0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
|
0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x43, 0x61, 0x64,
|
||||||
|
0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
|
0x65, 0x12, 0x49, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||||
|
0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e,
|
||||||
|
0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||||
|
0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24,
|
||||||
|
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64,
|
||||||
|
0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70,
|
||||||
|
0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_api_pb_caddy_storage_proto_rawDescOnce sync.Once
|
||||||
|
file_api_pb_caddy_storage_proto_rawDescData = file_api_pb_caddy_storage_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_api_pb_caddy_storage_proto_rawDescGZIP() []byte {
|
||||||
|
file_api_pb_caddy_storage_proto_rawDescOnce.Do(func() {
|
||||||
|
file_api_pb_caddy_storage_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_caddy_storage_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_api_pb_caddy_storage_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_api_pb_caddy_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||||
|
var file_api_pb_caddy_storage_proto_goTypes = []any{
|
||||||
|
(*StoreCaddyStorageRequest)(nil), // 0: api.StoreCaddyStorageRequest
|
||||||
|
(*LoadCaddyStorageRequest)(nil), // 1: api.LoadCaddyStorageRequest
|
||||||
|
(*LoadCaddyStorageResponse)(nil), // 2: api.LoadCaddyStorageResponse
|
||||||
|
(*DeleteCaddyStorageRequest)(nil), // 3: api.DeleteCaddyStorageRequest
|
||||||
|
(*ListCaddyStorageRequest)(nil), // 4: api.ListCaddyStorageRequest
|
||||||
|
(*ListCaddyStorageResponse)(nil), // 5: api.ListCaddyStorageResponse
|
||||||
|
(*StatCaddyStorageRequest)(nil), // 6: api.StatCaddyStorageRequest
|
||||||
|
(*StatCaddyStorageResponse)(nil), // 7: api.StatCaddyStorageResponse
|
||||||
|
(*ListCertificatesResponse)(nil), // 8: api.ListCertificatesResponse
|
||||||
|
(*IssuedCertificate)(nil), // 9: api.IssuedCertificate
|
||||||
|
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
||||||
|
(*emptypb.Empty)(nil), // 11: google.protobuf.Empty
|
||||||
|
}
|
||||||
|
var file_api_pb_caddy_storage_proto_depIdxs = []int32{
|
||||||
|
10, // 0: api.LoadCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
|
||||||
|
10, // 1: api.StatCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
|
||||||
|
9, // 2: api.ListCertificatesResponse.certificates:type_name -> api.IssuedCertificate
|
||||||
|
0, // 3: api.CaddyStorage.Store:input_type -> api.StoreCaddyStorageRequest
|
||||||
|
1, // 4: api.CaddyStorage.Load:input_type -> api.LoadCaddyStorageRequest
|
||||||
|
3, // 5: api.CaddyStorage.Delete:input_type -> api.DeleteCaddyStorageRequest
|
||||||
|
4, // 6: api.CaddyStorage.List:input_type -> api.ListCaddyStorageRequest
|
||||||
|
6, // 7: api.CaddyStorage.Stat:input_type -> api.StatCaddyStorageRequest
|
||||||
|
11, // 8: api.CaddyStorage.ListCertificates:input_type -> google.protobuf.Empty
|
||||||
|
11, // 9: api.CaddyStorage.Store:output_type -> google.protobuf.Empty
|
||||||
|
2, // 10: api.CaddyStorage.Load:output_type -> api.LoadCaddyStorageResponse
|
||||||
|
11, // 11: api.CaddyStorage.Delete:output_type -> google.protobuf.Empty
|
||||||
|
5, // 12: api.CaddyStorage.List:output_type -> api.ListCaddyStorageResponse
|
||||||
|
7, // 13: api.CaddyStorage.Stat:output_type -> api.StatCaddyStorageResponse
|
||||||
|
8, // 14: api.CaddyStorage.ListCertificates:output_type -> api.ListCertificatesResponse
|
||||||
|
9, // [9:15] is the sub-list for method output_type
|
||||||
|
3, // [3:9] is the sub-list for method input_type
|
||||||
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
|
0, // [0:3] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_api_pb_caddy_storage_proto_init() }
|
||||||
|
func file_api_pb_caddy_storage_proto_init() {
|
||||||
|
if File_api_pb_caddy_storage_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*StoreCaddyStorageRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*LoadCaddyStorageRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*LoadCaddyStorageResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*DeleteCaddyStorageRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*ListCaddyStorageRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*ListCaddyStorageResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*StatCaddyStorageRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*StatCaddyStorageResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*ListCertificatesResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_caddy_storage_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*IssuedCertificate); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_api_pb_caddy_storage_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 10,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_api_pb_caddy_storage_proto_goTypes,
|
||||||
|
DependencyIndexes: file_api_pb_caddy_storage_proto_depIdxs,
|
||||||
|
MessageInfos: file_api_pb_caddy_storage_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_api_pb_caddy_storage_proto = out.File
|
||||||
|
file_api_pb_caddy_storage_proto_rawDesc = nil
|
||||||
|
file_api_pb_caddy_storage_proto_goTypes = nil
|
||||||
|
file_api_pb_caddy_storage_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package api;
|
||||||
|
|
||||||
|
option go_package = "github.com/psviderski/uncloud/api/pb";
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
||||||
|
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
||||||
|
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
||||||
|
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
||||||
|
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
||||||
|
service CaddyStorage {
|
||||||
|
rpc Store(StoreCaddyStorageRequest) returns (google.protobuf.Empty);
|
||||||
|
rpc Load(LoadCaddyStorageRequest) returns (LoadCaddyStorageResponse);
|
||||||
|
rpc Delete(DeleteCaddyStorageRequest) returns (google.protobuf.Empty);
|
||||||
|
rpc List(ListCaddyStorageRequest) returns (ListCaddyStorageResponse);
|
||||||
|
rpc Stat(StatCaddyStorageRequest) returns (StatCaddyStorageResponse);
|
||||||
|
|
||||||
|
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
||||||
|
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
||||||
|
rpc ListCertificates(google.protobuf.Empty) returns (ListCertificatesResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message StoreCaddyStorageRequest {
|
||||||
|
string key = 1;
|
||||||
|
bytes value = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoadCaddyStorageRequest {
|
||||||
|
string key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoadCaddyStorageResponse {
|
||||||
|
bytes value = 1;
|
||||||
|
google.protobuf.Timestamp updated_at = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteCaddyStorageRequest {
|
||||||
|
string key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListCaddyStorageRequest {
|
||||||
|
string prefix = 1;
|
||||||
|
bool recursive = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListCaddyStorageResponse {
|
||||||
|
repeated string keys = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatCaddyStorageRequest {
|
||||||
|
string key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatCaddyStorageResponse {
|
||||||
|
string key = 1;
|
||||||
|
google.protobuf.Timestamp updated_at = 2;
|
||||||
|
int64 size = 3;
|
||||||
|
bool is_terminal = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListCertificatesResponse {
|
||||||
|
repeated IssuedCertificate certificates = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message IssuedCertificate {
|
||||||
|
// Subject Alternative Name (SAN) of the certificate. Caddy doesn't issue certificates with multiple SANs.
|
||||||
|
string san = 1;
|
||||||
|
// The PEM-encoding of DER-encoded ASN.1 data for the cert or chain, leaf first.
|
||||||
|
bytes chain = 2;
|
||||||
|
// Any extra information associated with the certificate, usually provided by the issuer implementation.
|
||||||
|
// JSON-encoded, may be absent or null.
|
||||||
|
bytes issuer_data = 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
|
// - protoc v5.27.3
|
||||||
|
// source: api/pb/caddy_storage.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
CaddyStorage_Store_FullMethodName = "/api.CaddyStorage/Store"
|
||||||
|
CaddyStorage_Load_FullMethodName = "/api.CaddyStorage/Load"
|
||||||
|
CaddyStorage_Delete_FullMethodName = "/api.CaddyStorage/Delete"
|
||||||
|
CaddyStorage_List_FullMethodName = "/api.CaddyStorage/List"
|
||||||
|
CaddyStorage_Stat_FullMethodName = "/api.CaddyStorage/Stat"
|
||||||
|
CaddyStorage_ListCertificates_FullMethodName = "/api.CaddyStorage/ListCertificates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CaddyStorageClient is the client API for CaddyStorage service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
//
|
||||||
|
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
||||||
|
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
||||||
|
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
||||||
|
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
||||||
|
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
||||||
|
type CaddyStorageClient interface {
|
||||||
|
Store(ctx context.Context, in *StoreCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
Load(ctx context.Context, in *LoadCaddyStorageRequest, opts ...grpc.CallOption) (*LoadCaddyStorageResponse, error)
|
||||||
|
Delete(ctx context.Context, in *DeleteCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
List(ctx context.Context, in *ListCaddyStorageRequest, opts ...grpc.CallOption) (*ListCaddyStorageResponse, error)
|
||||||
|
Stat(ctx context.Context, in *StatCaddyStorageRequest, opts ...grpc.CallOption) (*StatCaddyStorageResponse, error)
|
||||||
|
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
||||||
|
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
||||||
|
ListCertificates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListCertificatesResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type caddyStorageClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCaddyStorageClient(cc grpc.ClientConnInterface) CaddyStorageClient {
|
||||||
|
return &caddyStorageClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *caddyStorageClient) Store(ctx context.Context, in *StoreCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, CaddyStorage_Store_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *caddyStorageClient) Load(ctx context.Context, in *LoadCaddyStorageRequest, opts ...grpc.CallOption) (*LoadCaddyStorageResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(LoadCaddyStorageResponse)
|
||||||
|
err := c.cc.Invoke(ctx, CaddyStorage_Load_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *caddyStorageClient) Delete(ctx context.Context, in *DeleteCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, CaddyStorage_Delete_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *caddyStorageClient) List(ctx context.Context, in *ListCaddyStorageRequest, opts ...grpc.CallOption) (*ListCaddyStorageResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListCaddyStorageResponse)
|
||||||
|
err := c.cc.Invoke(ctx, CaddyStorage_List_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *caddyStorageClient) Stat(ctx context.Context, in *StatCaddyStorageRequest, opts ...grpc.CallOption) (*StatCaddyStorageResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(StatCaddyStorageResponse)
|
||||||
|
err := c.cc.Invoke(ctx, CaddyStorage_Stat_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *caddyStorageClient) ListCertificates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListCertificatesResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListCertificatesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, CaddyStorage_ListCertificates_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyStorageServer is the server API for CaddyStorage service.
|
||||||
|
// All implementations must embed UnimplementedCaddyStorageServer
|
||||||
|
// for forward compatibility.
|
||||||
|
//
|
||||||
|
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
||||||
|
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
||||||
|
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
||||||
|
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
||||||
|
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
||||||
|
type CaddyStorageServer interface {
|
||||||
|
Store(context.Context, *StoreCaddyStorageRequest) (*emptypb.Empty, error)
|
||||||
|
Load(context.Context, *LoadCaddyStorageRequest) (*LoadCaddyStorageResponse, error)
|
||||||
|
Delete(context.Context, *DeleteCaddyStorageRequest) (*emptypb.Empty, error)
|
||||||
|
List(context.Context, *ListCaddyStorageRequest) (*ListCaddyStorageResponse, error)
|
||||||
|
Stat(context.Context, *StatCaddyStorageRequest) (*StatCaddyStorageResponse, error)
|
||||||
|
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
||||||
|
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
||||||
|
ListCertificates(context.Context, *emptypb.Empty) (*ListCertificatesResponse, error)
|
||||||
|
mustEmbedUnimplementedCaddyStorageServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedCaddyStorageServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedCaddyStorageServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedCaddyStorageServer) Store(context.Context, *StoreCaddyStorageRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Store not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCaddyStorageServer) Load(context.Context, *LoadCaddyStorageRequest) (*LoadCaddyStorageResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Load not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCaddyStorageServer) Delete(context.Context, *DeleteCaddyStorageRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCaddyStorageServer) List(context.Context, *ListCaddyStorageRequest) (*ListCaddyStorageResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCaddyStorageServer) Stat(context.Context, *StatCaddyStorageRequest) (*StatCaddyStorageResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Stat not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCaddyStorageServer) ListCertificates(context.Context, *emptypb.Empty) (*ListCertificatesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListCertificates not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedCaddyStorageServer) mustEmbedUnimplementedCaddyStorageServer() {}
|
||||||
|
func (UnimplementedCaddyStorageServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeCaddyStorageServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to CaddyStorageServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeCaddyStorageServer interface {
|
||||||
|
mustEmbedUnimplementedCaddyStorageServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterCaddyStorageServer(s grpc.ServiceRegistrar, srv CaddyStorageServer) {
|
||||||
|
// If the following call pancis, it indicates UnimplementedCaddyStorageServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&CaddyStorage_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CaddyStorage_Store_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(StoreCaddyStorageRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CaddyStorageServer).Store(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CaddyStorage_Store_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CaddyStorageServer).Store(ctx, req.(*StoreCaddyStorageRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CaddyStorage_Load_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LoadCaddyStorageRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CaddyStorageServer).Load(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CaddyStorage_Load_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CaddyStorageServer).Load(ctx, req.(*LoadCaddyStorageRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CaddyStorage_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DeleteCaddyStorageRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CaddyStorageServer).Delete(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CaddyStorage_Delete_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CaddyStorageServer).Delete(ctx, req.(*DeleteCaddyStorageRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CaddyStorage_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListCaddyStorageRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CaddyStorageServer).List(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CaddyStorage_List_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CaddyStorageServer).List(ctx, req.(*ListCaddyStorageRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CaddyStorage_Stat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(StatCaddyStorageRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CaddyStorageServer).Stat(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CaddyStorage_Stat_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CaddyStorageServer).Stat(ctx, req.(*StatCaddyStorageRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CaddyStorage_ListCertificates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(CaddyStorageServer).ListCertificates(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: CaddyStorage_ListCertificates_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(CaddyStorageServer).ListCertificates(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyStorage_ServiceDesc is the grpc.ServiceDesc for CaddyStorage service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var CaddyStorage_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "api.CaddyStorage",
|
||||||
|
HandlerType: (*CaddyStorageServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "Store",
|
||||||
|
Handler: _CaddyStorage_Store_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Load",
|
||||||
|
Handler: _CaddyStorage_Load_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Delete",
|
||||||
|
Handler: _CaddyStorage_Delete_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "List",
|
||||||
|
Handler: _CaddyStorage_List_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Stat",
|
||||||
|
Handler: _CaddyStorage_Stat_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListCertificates",
|
||||||
|
Handler: _CaddyStorage_ListCertificates_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "api/pb/caddy_storage.proto",
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.34.2
|
// protoc-gen-go v1.34.2
|
||||||
// protoc v5.27.3
|
// protoc v5.27.3
|
||||||
// source: internal/machine/api/pb/cluster.proto
|
// source: api/pb/cluster.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -62,11 +62,11 @@ func (x MachineMember_MembershipState) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (MachineMember_MembershipState) Descriptor() protoreflect.EnumDescriptor {
|
func (MachineMember_MembershipState) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_internal_machine_api_pb_cluster_proto_enumTypes[0].Descriptor()
|
return file_api_pb_cluster_proto_enumTypes[0].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (MachineMember_MembershipState) Type() protoreflect.EnumType {
|
func (MachineMember_MembershipState) Type() protoreflect.EnumType {
|
||||||
return &file_internal_machine_api_pb_cluster_proto_enumTypes[0]
|
return &file_api_pb_cluster_proto_enumTypes[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x MachineMember_MembershipState) Number() protoreflect.EnumNumber {
|
func (x MachineMember_MembershipState) Number() protoreflect.EnumNumber {
|
||||||
@@ -75,7 +75,7 @@ func (x MachineMember_MembershipState) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use MachineMember_MembershipState.Descriptor instead.
|
// Deprecated: Use MachineMember_MembershipState.Descriptor instead.
|
||||||
func (MachineMember_MembershipState) EnumDescriptor() ([]byte, []int) {
|
func (MachineMember_MembershipState) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{2, 0}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{2, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRecord_RecordType int32
|
type DNSRecord_RecordType int32
|
||||||
@@ -111,11 +111,11 @@ func (x DNSRecord_RecordType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (DNSRecord_RecordType) Descriptor() protoreflect.EnumDescriptor {
|
func (DNSRecord_RecordType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
return file_internal_machine_api_pb_cluster_proto_enumTypes[1].Descriptor()
|
return file_api_pb_cluster_proto_enumTypes[1].Descriptor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (DNSRecord_RecordType) Type() protoreflect.EnumType {
|
func (DNSRecord_RecordType) Type() protoreflect.EnumType {
|
||||||
return &file_internal_machine_api_pb_cluster_proto_enumTypes[1]
|
return &file_api_pb_cluster_proto_enumTypes[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x DNSRecord_RecordType) Number() protoreflect.EnumNumber {
|
func (x DNSRecord_RecordType) Number() protoreflect.EnumNumber {
|
||||||
@@ -124,7 +124,7 @@ func (x DNSRecord_RecordType) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use DNSRecord_RecordType.Descriptor instead.
|
// Deprecated: Use DNSRecord_RecordType.Descriptor instead.
|
||||||
func (DNSRecord_RecordType) EnumDescriptor() ([]byte, []int) {
|
func (DNSRecord_RecordType) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{11, 0}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{10, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddMachineRequest struct {
|
type AddMachineRequest struct {
|
||||||
@@ -140,7 +140,7 @@ type AddMachineRequest struct {
|
|||||||
func (x *AddMachineRequest) Reset() {
|
func (x *AddMachineRequest) Reset() {
|
||||||
*x = AddMachineRequest{}
|
*x = AddMachineRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[0]
|
mi := &file_api_pb_cluster_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ func (x *AddMachineRequest) String() string {
|
|||||||
func (*AddMachineRequest) ProtoMessage() {}
|
func (*AddMachineRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *AddMachineRequest) ProtoReflect() protoreflect.Message {
|
func (x *AddMachineRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[0]
|
mi := &file_api_pb_cluster_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -166,7 +166,7 @@ func (x *AddMachineRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use AddMachineRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use AddMachineRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*AddMachineRequest) Descriptor() ([]byte, []int) {
|
func (*AddMachineRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{0}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddMachineRequest) GetName() string {
|
func (x *AddMachineRequest) GetName() string {
|
||||||
@@ -201,7 +201,7 @@ type AddMachineResponse struct {
|
|||||||
func (x *AddMachineResponse) Reset() {
|
func (x *AddMachineResponse) Reset() {
|
||||||
*x = AddMachineResponse{}
|
*x = AddMachineResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[1]
|
mi := &file_api_pb_cluster_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ func (x *AddMachineResponse) String() string {
|
|||||||
func (*AddMachineResponse) ProtoMessage() {}
|
func (*AddMachineResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *AddMachineResponse) ProtoReflect() protoreflect.Message {
|
func (x *AddMachineResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[1]
|
mi := &file_api_pb_cluster_proto_msgTypes[1]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -227,7 +227,7 @@ func (x *AddMachineResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use AddMachineResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use AddMachineResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*AddMachineResponse) Descriptor() ([]byte, []int) {
|
func (*AddMachineResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{1}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddMachineResponse) GetMachine() *MachineInfo {
|
func (x *AddMachineResponse) GetMachine() *MachineInfo {
|
||||||
@@ -249,7 +249,7 @@ type MachineMember struct {
|
|||||||
func (x *MachineMember) Reset() {
|
func (x *MachineMember) Reset() {
|
||||||
*x = MachineMember{}
|
*x = MachineMember{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[2]
|
mi := &file_api_pb_cluster_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ func (x *MachineMember) String() string {
|
|||||||
func (*MachineMember) ProtoMessage() {}
|
func (*MachineMember) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *MachineMember) ProtoReflect() protoreflect.Message {
|
func (x *MachineMember) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[2]
|
mi := &file_api_pb_cluster_proto_msgTypes[2]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -275,7 +275,7 @@ func (x *MachineMember) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use MachineMember.ProtoReflect.Descriptor instead.
|
// Deprecated: Use MachineMember.ProtoReflect.Descriptor instead.
|
||||||
func (*MachineMember) Descriptor() ([]byte, []int) {
|
func (*MachineMember) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{2}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *MachineMember) GetMachine() *MachineInfo {
|
func (x *MachineMember) GetMachine() *MachineInfo {
|
||||||
@@ -303,7 +303,7 @@ type ListMachinesResponse struct {
|
|||||||
func (x *ListMachinesResponse) Reset() {
|
func (x *ListMachinesResponse) Reset() {
|
||||||
*x = ListMachinesResponse{}
|
*x = ListMachinesResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[3]
|
mi := &file_api_pb_cluster_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -316,7 +316,7 @@ func (x *ListMachinesResponse) String() string {
|
|||||||
func (*ListMachinesResponse) ProtoMessage() {}
|
func (*ListMachinesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListMachinesResponse) ProtoReflect() protoreflect.Message {
|
func (x *ListMachinesResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[3]
|
mi := &file_api_pb_cluster_proto_msgTypes[3]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -329,7 +329,7 @@ func (x *ListMachinesResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ListMachinesResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListMachinesResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*ListMachinesResponse) Descriptor() ([]byte, []int) {
|
func (*ListMachinesResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{3}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListMachinesResponse) GetMachines() []*MachineMember {
|
func (x *ListMachinesResponse) GetMachines() []*MachineMember {
|
||||||
@@ -339,126 +339,6 @@ func (x *ListMachinesResponse) GetMachines() []*MachineMember {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateMachineRequest struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
// Machine to update
|
|
||||||
MachineId string `protobuf:"bytes,1,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"`
|
|
||||||
// Updated machine information
|
|
||||||
Name *string `protobuf:"bytes,2,opt,name=name,proto3,oneof" json:"name,omitempty"`
|
|
||||||
PublicIp *IP `protobuf:"bytes,3,opt,name=public_ip,json=publicIp,proto3,oneof" json:"public_ip,omitempty"`
|
|
||||||
Endpoints []*IPPort `protobuf:"bytes,4,rep,name=endpoints,proto3" json:"endpoints,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineRequest) Reset() {
|
|
||||||
*x = UpdateMachineRequest{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[4]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*UpdateMachineRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *UpdateMachineRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_internal_machine_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 UpdateMachineRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*UpdateMachineRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{4}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineRequest) GetMachineId() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.MachineId
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineRequest) GetName() string {
|
|
||||||
if x != nil && x.Name != nil {
|
|
||||||
return *x.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineRequest) GetPublicIp() *IP {
|
|
||||||
if x != nil {
|
|
||||||
return x.PublicIp
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineRequest) GetEndpoints() []*IPPort {
|
|
||||||
if x != nil {
|
|
||||||
return x.Endpoints
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateMachineResponse struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
Machine *MachineInfo `protobuf:"bytes,1,opt,name=machine,proto3" json:"machine,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineResponse) Reset() {
|
|
||||||
*x = UpdateMachineResponse{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[5]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*UpdateMachineResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *UpdateMachineResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_internal_machine_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 UpdateMachineResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*UpdateMachineResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *UpdateMachineResponse) GetMachine() *MachineInfo {
|
|
||||||
if x != nil {
|
|
||||||
return x.Machine
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoveMachineRequest struct {
|
type RemoveMachineRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -470,7 +350,7 @@ type RemoveMachineRequest struct {
|
|||||||
func (x *RemoveMachineRequest) Reset() {
|
func (x *RemoveMachineRequest) Reset() {
|
||||||
*x = RemoveMachineRequest{}
|
*x = RemoveMachineRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[6]
|
mi := &file_api_pb_cluster_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -483,7 +363,7 @@ func (x *RemoveMachineRequest) String() string {
|
|||||||
func (*RemoveMachineRequest) ProtoMessage() {}
|
func (*RemoveMachineRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RemoveMachineRequest) ProtoReflect() protoreflect.Message {
|
func (x *RemoveMachineRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[6]
|
mi := &file_api_pb_cluster_proto_msgTypes[4]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -496,7 +376,7 @@ func (x *RemoveMachineRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RemoveMachineRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RemoveMachineRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*RemoveMachineRequest) Descriptor() ([]byte, []int) {
|
func (*RemoveMachineRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{6}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RemoveMachineRequest) GetId() string {
|
func (x *RemoveMachineRequest) GetId() string {
|
||||||
@@ -512,12 +392,15 @@ type Domain struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
// Whether the domain is reserved in Uncloud DNS. Older daemons (<0.21) omit this field
|
||||||
|
// and only support reserved domains, so clients should treat absence as true.
|
||||||
|
Reserved *bool `protobuf:"varint,2,opt,name=reserved,proto3,oneof" json:"reserved,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Domain) Reset() {
|
func (x *Domain) Reset() {
|
||||||
*x = Domain{}
|
*x = Domain{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[7]
|
mi := &file_api_pb_cluster_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -530,7 +413,7 @@ func (x *Domain) String() string {
|
|||||||
func (*Domain) ProtoMessage() {}
|
func (*Domain) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Domain) ProtoReflect() protoreflect.Message {
|
func (x *Domain) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[7]
|
mi := &file_api_pb_cluster_proto_msgTypes[5]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -543,7 +426,7 @@ func (x *Domain) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
|
||||||
func (*Domain) Descriptor() ([]byte, []int) {
|
func (*Domain) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{7}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Domain) GetName() string {
|
func (x *Domain) GetName() string {
|
||||||
@@ -553,6 +436,13 @@ func (x *Domain) GetName() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Domain) GetReserved() bool {
|
||||||
|
if x != nil && x.Reserved != nil {
|
||||||
|
return *x.Reserved
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type ReserveDomainRequest struct {
|
type ReserveDomainRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -564,7 +454,7 @@ type ReserveDomainRequest struct {
|
|||||||
func (x *ReserveDomainRequest) Reset() {
|
func (x *ReserveDomainRequest) Reset() {
|
||||||
*x = ReserveDomainRequest{}
|
*x = ReserveDomainRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[8]
|
mi := &file_api_pb_cluster_proto_msgTypes[6]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -577,7 +467,7 @@ func (x *ReserveDomainRequest) String() string {
|
|||||||
func (*ReserveDomainRequest) ProtoMessage() {}
|
func (*ReserveDomainRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ReserveDomainRequest) ProtoReflect() protoreflect.Message {
|
func (x *ReserveDomainRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[8]
|
mi := &file_api_pb_cluster_proto_msgTypes[6]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -590,7 +480,7 @@ func (x *ReserveDomainRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ReserveDomainRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ReserveDomainRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*ReserveDomainRequest) Descriptor() ([]byte, []int) {
|
func (*ReserveDomainRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{8}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ReserveDomainRequest) GetEndpoint() string {
|
func (x *ReserveDomainRequest) GetEndpoint() string {
|
||||||
@@ -600,6 +490,54 @@ func (x *ReserveDomainRequest) GetEndpoint() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SetDomainRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// An externally managed domain name. Empty clears a manually set domain.
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SetDomainRequest) Reset() {
|
||||||
|
*x = SetDomainRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_cluster_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SetDomainRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SetDomainRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SetDomainRequest) 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 SetDomainRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SetDomainRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SetDomainRequest) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type CreateDomainRecordsRequest struct {
|
type CreateDomainRecordsRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -611,7 +549,7 @@ type CreateDomainRecordsRequest struct {
|
|||||||
func (x *CreateDomainRecordsRequest) Reset() {
|
func (x *CreateDomainRecordsRequest) Reset() {
|
||||||
*x = CreateDomainRecordsRequest{}
|
*x = CreateDomainRecordsRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[9]
|
mi := &file_api_pb_cluster_proto_msgTypes[8]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -624,7 +562,7 @@ func (x *CreateDomainRecordsRequest) String() string {
|
|||||||
func (*CreateDomainRecordsRequest) ProtoMessage() {}
|
func (*CreateDomainRecordsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message {
|
func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[9]
|
mi := &file_api_pb_cluster_proto_msgTypes[8]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -637,7 +575,7 @@ func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CreateDomainRecordsRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CreateDomainRecordsRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*CreateDomainRecordsRequest) Descriptor() ([]byte, []int) {
|
func (*CreateDomainRecordsRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{9}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{8}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateDomainRecordsRequest) GetRecords() []*DNSRecord {
|
func (x *CreateDomainRecordsRequest) GetRecords() []*DNSRecord {
|
||||||
@@ -658,7 +596,7 @@ type CreateDomainRecordsResponse struct {
|
|||||||
func (x *CreateDomainRecordsResponse) Reset() {
|
func (x *CreateDomainRecordsResponse) Reset() {
|
||||||
*x = CreateDomainRecordsResponse{}
|
*x = CreateDomainRecordsResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[10]
|
mi := &file_api_pb_cluster_proto_msgTypes[9]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -671,7 +609,7 @@ func (x *CreateDomainRecordsResponse) String() string {
|
|||||||
func (*CreateDomainRecordsResponse) ProtoMessage() {}
|
func (*CreateDomainRecordsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message {
|
func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[10]
|
mi := &file_api_pb_cluster_proto_msgTypes[9]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -684,7 +622,7 @@ func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use CreateDomainRecordsResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CreateDomainRecordsResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*CreateDomainRecordsResponse) Descriptor() ([]byte, []int) {
|
func (*CreateDomainRecordsResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{10}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateDomainRecordsResponse) GetRecords() []*DNSRecord {
|
func (x *CreateDomainRecordsResponse) GetRecords() []*DNSRecord {
|
||||||
@@ -707,7 +645,7 @@ type DNSRecord struct {
|
|||||||
func (x *DNSRecord) Reset() {
|
func (x *DNSRecord) Reset() {
|
||||||
*x = DNSRecord{}
|
*x = DNSRecord{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[11]
|
mi := &file_api_pb_cluster_proto_msgTypes[10]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -720,7 +658,7 @@ func (x *DNSRecord) String() string {
|
|||||||
func (*DNSRecord) ProtoMessage() {}
|
func (*DNSRecord) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *DNSRecord) ProtoReflect() protoreflect.Message {
|
func (x *DNSRecord) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[11]
|
mi := &file_api_pb_cluster_proto_msgTypes[10]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -733,7 +671,7 @@ func (x *DNSRecord) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use DNSRecord.ProtoReflect.Descriptor instead.
|
// Deprecated: Use DNSRecord.ProtoReflect.Descriptor instead.
|
||||||
func (*DNSRecord) Descriptor() ([]byte, []int) {
|
func (*DNSRecord) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{11}
|
return file_api_pb_cluster_proto_rawDescGZIP(), []int{10}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *DNSRecord) GetName() string {
|
func (x *DNSRecord) GetName() string {
|
||||||
@@ -757,71 +695,57 @@ func (x *DNSRecord) GetValues() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var File_internal_machine_api_pb_cluster_proto protoreflect.FileDescriptor
|
var File_api_pb_cluster_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_internal_machine_api_pb_cluster_proto_rawDesc = []byte{
|
var file_api_pb_cluster_proto_rawDesc = []byte{
|
||||||
0x0a, 0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
0x0a, 0x14, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
|
||||||
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f,
|
||||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f,
|
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70,
|
||||||
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d,
|
0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62,
|
||||||
0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x61,
|
||||||
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72,
|
||||||
0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
|
0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63,
|
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e,
|
0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x75,
|
||||||
0x2c, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e,
|
||||||
0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f,
|
0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70,
|
||||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x24, 0x0a,
|
0x22, 0x40, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
|
||||||
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69,
|
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61,
|
||||||
0x63, 0x49, 0x70, 0x22, 0x40, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63,
|
0x6e, 0x65, 0x22, 0xb4, 0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69,
|
0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18,
|
||||||
0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61,
|
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68,
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0xb4, 0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
0x12, 0x38, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||||
0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d,
|
0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x6d,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68,
|
0x62, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74,
|
||||||
0x69, 0x6e, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01,
|
0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3d, 0x0a, 0x0f, 0x4d, 0x65,
|
||||||
0x28, 0x0e, 0x32, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a,
|
||||||
0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69,
|
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x55, 0x50,
|
||||||
0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3d, 0x0a,
|
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x53, 0x50, 0x45, 0x43, 0x54, 0x10, 0x02, 0x12,
|
||||||
0x0f, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65,
|
0x08, 0x0a, 0x04, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73,
|
||||||
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x06, 0x0a,
|
0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x02, 0x55, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x53, 0x50, 0x45, 0x43, 0x54,
|
0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20,
|
||||||
0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x14,
|
0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
|
0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73,
|
0x73, 0x22, 0x26, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63,
|
0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68,
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4a, 0x0a, 0x06, 0x44, 0x6f, 0x6d,
|
||||||
0x69, 0x6e, 0x65, 0x73, 0x22, 0xbb, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d,
|
0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
|
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72,
|
||||||
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x76, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73,
|
||||||
0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x04,
|
0x65, 0x72, 0x76, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x73,
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61,
|
0x65, 0x72, 0x76, 0x65, 0x64, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||||
0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f,
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
|
||||||
0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49,
|
0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x50, 0x48, 0x01, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x88, 0x01, 0x01,
|
0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x26, 0x0a, 0x10, 0x53, 0x65, 0x74,
|
||||||
0x12, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20,
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
|
||||||
0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74,
|
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
|
||||||
0x52, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f,
|
0x65, 0x22, 0x46, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f,
|
|
||||||
0x69, 0x70, 0x22, 0x43, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 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, 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,
|
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,
|
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,
|
0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||||
@@ -839,7 +763,7 @@ var file_internal_machine_api_pb_cluster_proto_rawDesc = []byte{
|
|||||||
0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52,
|
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,
|
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,
|
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, 0x92, 0x04, 0x0a, 0x07,
|
0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0x86, 0x04, 0x0a, 0x07,
|
||||||
0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x4d, 0x61,
|
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,
|
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, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,
|
||||||
@@ -848,118 +772,111 @@ var file_internal_machine_api_pb_cluster_proto_rawDesc = []byte{
|
|||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
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,
|
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,
|
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, 0x46, 0x0a, 0x0d, 0x55, 0x70, 0x64,
|
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d,
|
||||||
0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69,
|
0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69,
|
||||||
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
|
0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a,
|
||||||
0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19,
|
||||||
0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d,
|
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
|
0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d,
|
||||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65,
|
0x61, 0x69, 0x6e, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d,
|
||||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73,
|
0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||||
0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||||
0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30,
|
0x74, 0x79, 0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
||||||
0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f,
|
||||||
0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44,
|
||||||
0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e,
|
||||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72,
|
||||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1f, 0x2e,
|
0x73, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f,
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20,
|
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44,
|
||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||||
0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
|
0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e,
|
||||||
0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75,
|
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||||
0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_internal_machine_api_pb_cluster_proto_rawDescOnce sync.Once
|
file_api_pb_cluster_proto_rawDescOnce sync.Once
|
||||||
file_internal_machine_api_pb_cluster_proto_rawDescData = file_internal_machine_api_pb_cluster_proto_rawDesc
|
file_api_pb_cluster_proto_rawDescData = file_api_pb_cluster_proto_rawDesc
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_internal_machine_api_pb_cluster_proto_rawDescGZIP() []byte {
|
func file_api_pb_cluster_proto_rawDescGZIP() []byte {
|
||||||
file_internal_machine_api_pb_cluster_proto_rawDescOnce.Do(func() {
|
file_api_pb_cluster_proto_rawDescOnce.Do(func() {
|
||||||
file_internal_machine_api_pb_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_machine_api_pb_cluster_proto_rawDescData)
|
file_api_pb_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_cluster_proto_rawDescData)
|
||||||
})
|
})
|
||||||
return file_internal_machine_api_pb_cluster_proto_rawDescData
|
return file_api_pb_cluster_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_internal_machine_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
var file_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_internal_machine_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
|
var file_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||||
var file_internal_machine_api_pb_cluster_proto_goTypes = []any{
|
var file_api_pb_cluster_proto_goTypes = []any{
|
||||||
(MachineMember_MembershipState)(0), // 0: api.MachineMember.MembershipState
|
(MachineMember_MembershipState)(0), // 0: api.MachineMember.MembershipState
|
||||||
(DNSRecord_RecordType)(0), // 1: api.DNSRecord.RecordType
|
(DNSRecord_RecordType)(0), // 1: api.DNSRecord.RecordType
|
||||||
(*AddMachineRequest)(nil), // 2: api.AddMachineRequest
|
(*AddMachineRequest)(nil), // 2: api.AddMachineRequest
|
||||||
(*AddMachineResponse)(nil), // 3: api.AddMachineResponse
|
(*AddMachineResponse)(nil), // 3: api.AddMachineResponse
|
||||||
(*MachineMember)(nil), // 4: api.MachineMember
|
(*MachineMember)(nil), // 4: api.MachineMember
|
||||||
(*ListMachinesResponse)(nil), // 5: api.ListMachinesResponse
|
(*ListMachinesResponse)(nil), // 5: api.ListMachinesResponse
|
||||||
(*UpdateMachineRequest)(nil), // 6: api.UpdateMachineRequest
|
(*RemoveMachineRequest)(nil), // 6: api.RemoveMachineRequest
|
||||||
(*UpdateMachineResponse)(nil), // 7: api.UpdateMachineResponse
|
(*Domain)(nil), // 7: api.Domain
|
||||||
(*RemoveMachineRequest)(nil), // 8: api.RemoveMachineRequest
|
(*ReserveDomainRequest)(nil), // 8: api.ReserveDomainRequest
|
||||||
(*Domain)(nil), // 9: api.Domain
|
(*SetDomainRequest)(nil), // 9: api.SetDomainRequest
|
||||||
(*ReserveDomainRequest)(nil), // 10: api.ReserveDomainRequest
|
(*CreateDomainRecordsRequest)(nil), // 10: api.CreateDomainRecordsRequest
|
||||||
(*CreateDomainRecordsRequest)(nil), // 11: api.CreateDomainRecordsRequest
|
(*CreateDomainRecordsResponse)(nil), // 11: api.CreateDomainRecordsResponse
|
||||||
(*CreateDomainRecordsResponse)(nil), // 12: api.CreateDomainRecordsResponse
|
(*DNSRecord)(nil), // 12: api.DNSRecord
|
||||||
(*DNSRecord)(nil), // 13: api.DNSRecord
|
(*NetworkConfig)(nil), // 13: api.NetworkConfig
|
||||||
(*NetworkConfig)(nil), // 14: api.NetworkConfig
|
(*IP)(nil), // 14: api.IP
|
||||||
(*IP)(nil), // 15: api.IP
|
(*MachineInfo)(nil), // 15: api.MachineInfo
|
||||||
(*MachineInfo)(nil), // 16: api.MachineInfo
|
(*emptypb.Empty)(nil), // 16: google.protobuf.Empty
|
||||||
(*IPPort)(nil), // 17: api.IPPort
|
|
||||||
(*emptypb.Empty)(nil), // 18: google.protobuf.Empty
|
|
||||||
}
|
}
|
||||||
var file_internal_machine_api_pb_cluster_proto_depIdxs = []int32{
|
var file_api_pb_cluster_proto_depIdxs = []int32{
|
||||||
14, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig
|
13, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig
|
||||||
15, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP
|
14, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP
|
||||||
16, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo
|
15, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo
|
||||||
16, // 3: api.MachineMember.machine:type_name -> api.MachineInfo
|
15, // 3: api.MachineMember.machine:type_name -> api.MachineInfo
|
||||||
0, // 4: api.MachineMember.state:type_name -> api.MachineMember.MembershipState
|
0, // 4: api.MachineMember.state:type_name -> api.MachineMember.MembershipState
|
||||||
4, // 5: api.ListMachinesResponse.machines:type_name -> api.MachineMember
|
4, // 5: api.ListMachinesResponse.machines:type_name -> api.MachineMember
|
||||||
15, // 6: api.UpdateMachineRequest.public_ip:type_name -> api.IP
|
12, // 6: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord
|
||||||
17, // 7: api.UpdateMachineRequest.endpoints:type_name -> api.IPPort
|
12, // 7: api.CreateDomainRecordsResponse.records:type_name -> api.DNSRecord
|
||||||
16, // 8: api.UpdateMachineResponse.machine:type_name -> api.MachineInfo
|
1, // 8: api.DNSRecord.type:type_name -> api.DNSRecord.RecordType
|
||||||
13, // 9: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord
|
2, // 9: api.Cluster.AddMachine:input_type -> api.AddMachineRequest
|
||||||
13, // 10: api.CreateDomainRecordsResponse.records:type_name -> api.DNSRecord
|
16, // 10: api.Cluster.ListMachines:input_type -> google.protobuf.Empty
|
||||||
1, // 11: api.DNSRecord.type:type_name -> api.DNSRecord.RecordType
|
6, // 11: api.Cluster.RemoveMachine:input_type -> api.RemoveMachineRequest
|
||||||
2, // 12: api.Cluster.AddMachine:input_type -> api.AddMachineRequest
|
8, // 12: api.Cluster.ReserveDomain:input_type -> api.ReserveDomainRequest
|
||||||
18, // 13: api.Cluster.ListMachines:input_type -> google.protobuf.Empty
|
9, // 13: api.Cluster.SetDomain:input_type -> api.SetDomainRequest
|
||||||
6, // 14: api.Cluster.UpdateMachine:input_type -> api.UpdateMachineRequest
|
16, // 14: api.Cluster.GetDomain:input_type -> google.protobuf.Empty
|
||||||
8, // 15: api.Cluster.RemoveMachine:input_type -> api.RemoveMachineRequest
|
16, // 15: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty
|
||||||
10, // 16: api.Cluster.ReserveDomain:input_type -> api.ReserveDomainRequest
|
10, // 16: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest
|
||||||
18, // 17: api.Cluster.GetDomain:input_type -> google.protobuf.Empty
|
3, // 17: api.Cluster.AddMachine:output_type -> api.AddMachineResponse
|
||||||
18, // 18: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty
|
5, // 18: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse
|
||||||
11, // 19: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest
|
16, // 19: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty
|
||||||
3, // 20: api.Cluster.AddMachine:output_type -> api.AddMachineResponse
|
7, // 20: api.Cluster.ReserveDomain:output_type -> api.Domain
|
||||||
5, // 21: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse
|
16, // 21: api.Cluster.SetDomain:output_type -> google.protobuf.Empty
|
||||||
7, // 22: api.Cluster.UpdateMachine:output_type -> api.UpdateMachineResponse
|
7, // 22: api.Cluster.GetDomain:output_type -> api.Domain
|
||||||
18, // 23: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty
|
7, // 23: api.Cluster.ReleaseDomain:output_type -> api.Domain
|
||||||
9, // 24: api.Cluster.ReserveDomain:output_type -> api.Domain
|
11, // 24: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
|
||||||
9, // 25: api.Cluster.GetDomain:output_type -> api.Domain
|
17, // [17:25] is the sub-list for method output_type
|
||||||
9, // 26: api.Cluster.ReleaseDomain:output_type -> api.Domain
|
9, // [9:17] is the sub-list for method input_type
|
||||||
12, // 27: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
|
9, // [9:9] is the sub-list for extension type_name
|
||||||
20, // [20:28] is the sub-list for method output_type
|
9, // [9:9] is the sub-list for extension extendee
|
||||||
12, // [12:20] is the sub-list for method input_type
|
0, // [0:9] is the sub-list for field type_name
|
||||||
12, // [12:12] is the sub-list for extension type_name
|
|
||||||
12, // [12:12] is the sub-list for extension extendee
|
|
||||||
0, // [0:12] is the sub-list for field type_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_internal_machine_api_pb_cluster_proto_init() }
|
func init() { file_api_pb_cluster_proto_init() }
|
||||||
func file_internal_machine_api_pb_cluster_proto_init() {
|
func file_api_pb_cluster_proto_init() {
|
||||||
if File_internal_machine_api_pb_cluster_proto != nil {
|
if File_api_pb_cluster_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_common_proto_init()
|
file_api_pb_common_proto_init()
|
||||||
file_internal_machine_api_pb_machine_proto_init()
|
file_api_pb_machine_proto_init()
|
||||||
if !protoimpl.UnsafeEnabled {
|
if !protoimpl.UnsafeEnabled {
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*AddMachineRequest); i {
|
switch v := v.(*AddMachineRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -971,7 +888,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*AddMachineResponse); i {
|
switch v := v.(*AddMachineResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -983,7 +900,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*MachineMember); i {
|
switch v := v.(*MachineMember); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -995,7 +912,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*ListMachinesResponse); i {
|
switch v := v.(*ListMachinesResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1007,31 +924,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*UpdateMachineRequest); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*UpdateMachineResponse); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*RemoveMachineRequest); i {
|
switch v := v.(*RemoveMachineRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1043,7 +936,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*Domain); i {
|
switch v := v.(*Domain); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1055,7 +948,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*ReserveDomainRequest); i {
|
switch v := v.(*ReserveDomainRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1067,7 +960,19 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*SetDomainRequest); 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.(*CreateDomainRecordsRequest); i {
|
switch v := v.(*CreateDomainRecordsRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1079,7 +984,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[10].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*CreateDomainRecordsResponse); i {
|
switch v := v.(*CreateDomainRecordsResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1091,7 +996,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[11].Exporter = func(v any, i int) any {
|
file_api_pb_cluster_proto_msgTypes[10].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*DNSRecord); i {
|
switch v := v.(*DNSRecord); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1104,24 +1009,24 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_cluster_proto_msgTypes[4].OneofWrappers = []any{}
|
file_api_pb_cluster_proto_msgTypes[5].OneofWrappers = []any{}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_internal_machine_api_pb_cluster_proto_rawDesc,
|
RawDescriptor: file_api_pb_cluster_proto_rawDesc,
|
||||||
NumEnums: 2,
|
NumEnums: 2,
|
||||||
NumMessages: 12,
|
NumMessages: 11,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
GoTypes: file_internal_machine_api_pb_cluster_proto_goTypes,
|
GoTypes: file_api_pb_cluster_proto_goTypes,
|
||||||
DependencyIndexes: file_internal_machine_api_pb_cluster_proto_depIdxs,
|
DependencyIndexes: file_api_pb_cluster_proto_depIdxs,
|
||||||
EnumInfos: file_internal_machine_api_pb_cluster_proto_enumTypes,
|
EnumInfos: file_api_pb_cluster_proto_enumTypes,
|
||||||
MessageInfos: file_internal_machine_api_pb_cluster_proto_msgTypes,
|
MessageInfos: file_api_pb_cluster_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_internal_machine_api_pb_cluster_proto = out.File
|
File_api_pb_cluster_proto = out.File
|
||||||
file_internal_machine_api_pb_cluster_proto_rawDesc = nil
|
file_api_pb_cluster_proto_rawDesc = nil
|
||||||
file_internal_machine_api_pb_cluster_proto_goTypes = nil
|
file_api_pb_cluster_proto_goTypes = nil
|
||||||
file_internal_machine_api_pb_cluster_proto_depIdxs = nil
|
file_api_pb_cluster_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,19 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package api;
|
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/empty.proto";
|
||||||
import "internal/machine/api/pb/common.proto";
|
import "api/pb/common.proto";
|
||||||
import "internal/machine/api/pb/machine.proto";
|
import "api/pb/machine.proto";
|
||||||
|
|
||||||
service Cluster {
|
service Cluster {
|
||||||
rpc AddMachine(AddMachineRequest) returns (AddMachineResponse);
|
rpc AddMachine(AddMachineRequest) returns (AddMachineResponse);
|
||||||
rpc ListMachines(google.protobuf.Empty) returns (ListMachinesResponse);
|
rpc ListMachines(google.protobuf.Empty) returns (ListMachinesResponse);
|
||||||
rpc UpdateMachine(UpdateMachineRequest) returns (UpdateMachineResponse);
|
|
||||||
rpc RemoveMachine(RemoveMachineRequest) returns (google.protobuf.Empty);
|
rpc RemoveMachine(RemoveMachineRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
rpc ReserveDomain(ReserveDomainRequest) returns (Domain);
|
rpc ReserveDomain(ReserveDomainRequest) returns (Domain);
|
||||||
|
rpc SetDomain(SetDomainRequest) returns (google.protobuf.Empty);
|
||||||
rpc GetDomain(google.protobuf.Empty) returns (Domain);
|
rpc GetDomain(google.protobuf.Empty) returns (Domain);
|
||||||
rpc ReleaseDomain(google.protobuf.Empty) returns (Domain);
|
rpc ReleaseDomain(google.protobuf.Empty) returns (Domain);
|
||||||
rpc CreateDomainRecords(CreateDomainRecordsRequest) returns (CreateDomainRecordsResponse);
|
rpc CreateDomainRecords(CreateDomainRecordsRequest) returns (CreateDomainRecordsResponse);
|
||||||
@@ -51,32 +51,26 @@ message ListMachinesResponse {
|
|||||||
repeated MachineMember machines = 1;
|
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 {
|
message RemoveMachineRequest {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Domain {
|
message Domain {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
|
// Whether the domain is reserved in Uncloud DNS. Older daemons (<0.21) omit this field
|
||||||
|
// and only support reserved domains, so clients should treat absence as true.
|
||||||
|
optional bool reserved = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ReserveDomainRequest {
|
message ReserveDomainRequest {
|
||||||
string endpoint = 1;
|
string endpoint = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SetDomainRequest {
|
||||||
|
// An externally managed domain name. Empty clears a manually set domain.
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message CreateDomainRecordsRequest {
|
message CreateDomainRecordsRequest {
|
||||||
repeated DNSRecord records = 1;
|
repeated DNSRecord records = 1;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
// - protoc v5.27.3
|
// - protoc v5.27.3
|
||||||
// source: internal/machine/api/pb/cluster.proto
|
// source: api/pb/cluster.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ const _ = grpc.SupportPackageIsVersion9
|
|||||||
const (
|
const (
|
||||||
Cluster_AddMachine_FullMethodName = "/api.Cluster/AddMachine"
|
Cluster_AddMachine_FullMethodName = "/api.Cluster/AddMachine"
|
||||||
Cluster_ListMachines_FullMethodName = "/api.Cluster/ListMachines"
|
Cluster_ListMachines_FullMethodName = "/api.Cluster/ListMachines"
|
||||||
Cluster_UpdateMachine_FullMethodName = "/api.Cluster/UpdateMachine"
|
|
||||||
Cluster_RemoveMachine_FullMethodName = "/api.Cluster/RemoveMachine"
|
Cluster_RemoveMachine_FullMethodName = "/api.Cluster/RemoveMachine"
|
||||||
Cluster_ReserveDomain_FullMethodName = "/api.Cluster/ReserveDomain"
|
Cluster_ReserveDomain_FullMethodName = "/api.Cluster/ReserveDomain"
|
||||||
|
Cluster_SetDomain_FullMethodName = "/api.Cluster/SetDomain"
|
||||||
Cluster_GetDomain_FullMethodName = "/api.Cluster/GetDomain"
|
Cluster_GetDomain_FullMethodName = "/api.Cluster/GetDomain"
|
||||||
Cluster_ReleaseDomain_FullMethodName = "/api.Cluster/ReleaseDomain"
|
Cluster_ReleaseDomain_FullMethodName = "/api.Cluster/ReleaseDomain"
|
||||||
Cluster_CreateDomainRecords_FullMethodName = "/api.Cluster/CreateDomainRecords"
|
Cluster_CreateDomainRecords_FullMethodName = "/api.Cluster/CreateDomainRecords"
|
||||||
@@ -36,9 +36,9 @@ const (
|
|||||||
type ClusterClient interface {
|
type ClusterClient interface {
|
||||||
AddMachine(ctx context.Context, in *AddMachineRequest, opts ...grpc.CallOption) (*AddMachineResponse, error)
|
AddMachine(ctx context.Context, in *AddMachineRequest, opts ...grpc.CallOption) (*AddMachineResponse, error)
|
||||||
ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListMachinesResponse, 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)
|
RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error)
|
ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error)
|
||||||
|
SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
|
GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
|
||||||
ReleaseDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
|
ReleaseDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
|
||||||
CreateDomainRecords(ctx context.Context, in *CreateDomainRecordsRequest, opts ...grpc.CallOption) (*CreateDomainRecordsResponse, error)
|
CreateDomainRecords(ctx context.Context, in *CreateDomainRecordsRequest, opts ...grpc.CallOption) (*CreateDomainRecordsResponse, error)
|
||||||
@@ -72,16 +72,6 @@ func (c *clusterClient) ListMachines(ctx context.Context, in *emptypb.Empty, opt
|
|||||||
return out, nil
|
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) {
|
func (c *clusterClient) RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(emptypb.Empty)
|
out := new(emptypb.Empty)
|
||||||
@@ -102,6 +92,16 @@ func (c *clusterClient) ReserveDomain(ctx context.Context, in *ReserveDomainRequ
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *clusterClient) SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, Cluster_SetDomain_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *clusterClient) GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error) {
|
func (c *clusterClient) GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(Domain)
|
out := new(Domain)
|
||||||
@@ -138,9 +138,9 @@ func (c *clusterClient) CreateDomainRecords(ctx context.Context, in *CreateDomai
|
|||||||
type ClusterServer interface {
|
type ClusterServer interface {
|
||||||
AddMachine(context.Context, *AddMachineRequest) (*AddMachineResponse, error)
|
AddMachine(context.Context, *AddMachineRequest) (*AddMachineResponse, error)
|
||||||
ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error)
|
ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error)
|
||||||
UpdateMachine(context.Context, *UpdateMachineRequest) (*UpdateMachineResponse, error)
|
|
||||||
RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error)
|
RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error)
|
||||||
ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error)
|
ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error)
|
||||||
|
SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error)
|
||||||
GetDomain(context.Context, *emptypb.Empty) (*Domain, error)
|
GetDomain(context.Context, *emptypb.Empty) (*Domain, error)
|
||||||
ReleaseDomain(context.Context, *emptypb.Empty) (*Domain, error)
|
ReleaseDomain(context.Context, *emptypb.Empty) (*Domain, error)
|
||||||
CreateDomainRecords(context.Context, *CreateDomainRecordsRequest) (*CreateDomainRecordsResponse, error)
|
CreateDomainRecords(context.Context, *CreateDomainRecordsRequest) (*CreateDomainRecordsResponse, error)
|
||||||
@@ -160,15 +160,15 @@ func (UnimplementedClusterServer) AddMachine(context.Context, *AddMachineRequest
|
|||||||
func (UnimplementedClusterServer) ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error) {
|
func (UnimplementedClusterServer) ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListMachines not implemented")
|
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) {
|
func (UnimplementedClusterServer) RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method RemoveMachine not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method RemoveMachine not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedClusterServer) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) {
|
func (UnimplementedClusterServer) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ReserveDomain not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ReserveDomain not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedClusterServer) SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SetDomain not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedClusterServer) GetDomain(context.Context, *emptypb.Empty) (*Domain, error) {
|
func (UnimplementedClusterServer) GetDomain(context.Context, *emptypb.Empty) (*Domain, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetDomain not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetDomain not implemented")
|
||||||
}
|
}
|
||||||
@@ -235,24 +235,6 @@ func _Cluster_ListMachines_Handler(srv interface{}, ctx context.Context, dec fun
|
|||||||
return interceptor(ctx, in, info, handler)
|
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) {
|
func _Cluster_RemoveMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(RemoveMachineRequest)
|
in := new(RemoveMachineRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -289,6 +271,24 @@ func _Cluster_ReserveDomain_Handler(srv interface{}, ctx context.Context, dec fu
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Cluster_SetDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SetDomainRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ClusterServer).SetDomain(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Cluster_SetDomain_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ClusterServer).SetDomain(ctx, req.(*SetDomainRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _Cluster_GetDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Cluster_GetDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(emptypb.Empty)
|
in := new(emptypb.Empty)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -358,10 +358,6 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ListMachines",
|
MethodName: "ListMachines",
|
||||||
Handler: _Cluster_ListMachines_Handler,
|
Handler: _Cluster_ListMachines_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "UpdateMachine",
|
|
||||||
Handler: _Cluster_UpdateMachine_Handler,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
MethodName: "RemoveMachine",
|
MethodName: "RemoveMachine",
|
||||||
Handler: _Cluster_RemoveMachine_Handler,
|
Handler: _Cluster_RemoveMachine_Handler,
|
||||||
@@ -370,6 +366,10 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ReserveDomain",
|
MethodName: "ReserveDomain",
|
||||||
Handler: _Cluster_ReserveDomain_Handler,
|
Handler: _Cluster_ReserveDomain_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetDomain",
|
||||||
|
Handler: _Cluster_SetDomain_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "GetDomain",
|
MethodName: "GetDomain",
|
||||||
Handler: _Cluster_GetDomain_Handler,
|
Handler: _Cluster_GetDomain_Handler,
|
||||||
@@ -384,5 +384,5 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "internal/machine/api/pb/cluster.proto",
|
Metadata: "api/pb/cluster.proto",
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,16 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Err returns the upstream error, preserving its gRPC status code.
|
||||||
|
// It returns nil for nil metadata or an absent or successful status.
|
||||||
|
func (m *Metadata) Err() error {
|
||||||
|
return status.ErrorProto(m.GetStatus())
|
||||||
|
}
|
||||||
|
|
||||||
func NewIP(addr netip.Addr) *IP {
|
func NewIP(addr netip.Addr) *IP {
|
||||||
// MarshalBinary always returns a nil error.
|
// MarshalBinary always returns a nil error.
|
||||||
ip, _ := addr.MarshalBinary()
|
ip, _ := addr.MarshalBinary()
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.34.2
|
// protoc-gen-go v1.34.2
|
||||||
// protoc v5.27.3
|
// protoc v5.27.3
|
||||||
// source: internal/machine/api/pb/common.proto
|
// source: api/pb/common.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -58,11 +58,11 @@ func (x LogEntry_StreamType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (LogEntry_StreamType) Descriptor() protoreflect.EnumDescriptor {
|
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 {
|
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 {
|
func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
|
||||||
@@ -71,7 +71,7 @@ func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
|
|||||||
|
|
||||||
// Deprecated: Use LogEntry_StreamType.Descriptor instead.
|
// Deprecated: Use LogEntry_StreamType.Descriptor instead.
|
||||||
func (LogEntry_StreamType) EnumDescriptor() ([]byte, []int) {
|
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
|
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
|
||||||
@@ -81,9 +81,14 @@ type Metadata struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
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.
|
// 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 is set if the request to upstream failed. The rest of the response apart from this metadata message
|
||||||
|
// is undefined.
|
||||||
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
|
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
|
||||||
// error as a gRPC Status message.
|
// error as a gRPC Status message.
|
||||||
Status *status.Status `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
|
Status *status.Status `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
@@ -92,7 +97,7 @@ type Metadata struct {
|
|||||||
func (x *Metadata) Reset() {
|
func (x *Metadata) Reset() {
|
||||||
*x = Metadata{}
|
*x = Metadata{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -105,7 +110,7 @@ func (x *Metadata) String() string {
|
|||||||
func (*Metadata) ProtoMessage() {}
|
func (*Metadata) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Metadata) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -118,12 +123,26 @@ func (x *Metadata) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
|
||||||
func (*Metadata) Descriptor() ([]byte, []int) {
|
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 {
|
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 ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -154,7 +173,7 @@ type Empty struct {
|
|||||||
func (x *Empty) Reset() {
|
func (x *Empty) Reset() {
|
||||||
*x = Empty{}
|
*x = Empty{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -167,7 +186,7 @@ func (x *Empty) String() string {
|
|||||||
func (*Empty) ProtoMessage() {}
|
func (*Empty) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Empty) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -180,7 +199,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
|
||||||
func (*Empty) Descriptor() ([]byte, []int) {
|
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 {
|
func (x *Empty) GetMetadata() *Metadata {
|
||||||
@@ -203,7 +222,7 @@ type EmptyResponse struct {
|
|||||||
func (x *EmptyResponse) Reset() {
|
func (x *EmptyResponse) Reset() {
|
||||||
*x = EmptyResponse{}
|
*x = EmptyResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -216,7 +235,7 @@ func (x *EmptyResponse) String() string {
|
|||||||
func (*EmptyResponse) ProtoMessage() {}
|
func (*EmptyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -229,7 +248,7 @@ func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*EmptyResponse) Descriptor() ([]byte, []int) {
|
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 {
|
func (x *EmptyResponse) GetMessages() []*Empty {
|
||||||
@@ -250,7 +269,7 @@ type IP struct {
|
|||||||
func (x *IP) Reset() {
|
func (x *IP) Reset() {
|
||||||
*x = IP{}
|
*x = IP{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -263,7 +282,7 @@ func (x *IP) String() string {
|
|||||||
func (*IP) ProtoMessage() {}
|
func (*IP) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *IP) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -276,7 +295,7 @@ func (x *IP) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use IP.ProtoReflect.Descriptor instead.
|
// Deprecated: Use IP.ProtoReflect.Descriptor instead.
|
||||||
func (*IP) Descriptor() ([]byte, []int) {
|
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 {
|
func (x *IP) GetIp() []byte {
|
||||||
@@ -298,7 +317,7 @@ type IPPort struct {
|
|||||||
func (x *IPPort) Reset() {
|
func (x *IPPort) Reset() {
|
||||||
*x = IPPort{}
|
*x = IPPort{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -311,7 +330,7 @@ func (x *IPPort) String() string {
|
|||||||
func (*IPPort) ProtoMessage() {}
|
func (*IPPort) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *IPPort) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -324,7 +343,7 @@ func (x *IPPort) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use IPPort.ProtoReflect.Descriptor instead.
|
// Deprecated: Use IPPort.ProtoReflect.Descriptor instead.
|
||||||
func (*IPPort) Descriptor() ([]byte, []int) {
|
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 {
|
func (x *IPPort) GetIp() *IP {
|
||||||
@@ -353,7 +372,7 @@ type IPPrefix struct {
|
|||||||
func (x *IPPrefix) Reset() {
|
func (x *IPPrefix) Reset() {
|
||||||
*x = IPPrefix{}
|
*x = IPPrefix{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -366,7 +385,7 @@ func (x *IPPrefix) String() string {
|
|||||||
func (*IPPrefix) ProtoMessage() {}
|
func (*IPPrefix) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *IPPrefix) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -379,7 +398,7 @@ func (x *IPPrefix) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use IPPrefix.ProtoReflect.Descriptor instead.
|
// Deprecated: Use IPPrefix.ProtoReflect.Descriptor instead.
|
||||||
func (*IPPrefix) Descriptor() ([]byte, []int) {
|
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 {
|
func (x *IPPrefix) GetIp() *IP {
|
||||||
@@ -412,7 +431,7 @@ type LogsRequest struct {
|
|||||||
func (x *LogsRequest) Reset() {
|
func (x *LogsRequest) Reset() {
|
||||||
*x = LogsRequest{}
|
*x = LogsRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -425,7 +444,7 @@ func (x *LogsRequest) String() string {
|
|||||||
func (*LogsRequest) ProtoMessage() {}
|
func (*LogsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *LogsRequest) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -438,7 +457,7 @@ func (x *LogsRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use LogsRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use LogsRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*LogsRequest) Descriptor() ([]byte, []int) {
|
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 {
|
func (x *LogsRequest) GetId() string {
|
||||||
@@ -490,7 +509,7 @@ type LogEntry struct {
|
|||||||
func (x *LogEntry) Reset() {
|
func (x *LogEntry) Reset() {
|
||||||
*x = LogEntry{}
|
*x = LogEntry{}
|
||||||
if protoimpl.UnsafeEnabled {
|
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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -503,7 +522,7 @@ func (x *LogEntry) String() string {
|
|||||||
func (*LogEntry) ProtoMessage() {}
|
func (*LogEntry) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *LogEntry) ProtoReflect() protoreflect.Message {
|
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 {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -516,7 +535,7 @@ func (x *LogEntry) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
|
// Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
|
||||||
func (*LogEntry) Descriptor() ([]byte, []int) {
|
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 {
|
func (x *LogEntry) GetStream() LogEntry_StreamType {
|
||||||
@@ -540,79 +559,82 @@ func (x *LogEntry) GetMessage() []byte {
|
|||||||
return nil
|
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{
|
var file_api_pb_common_proto_rawDesc = []byte{
|
||||||
0x0a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
0x0a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f,
|
0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72,
|
||||||
0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70,
|
0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
|
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,
|
||||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
|
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x66, 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,
|
||||||
0x61, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01,
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
|
||||||
0x28, 0x09, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
|
0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e,
|
||||||
0x72, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x61,
|
||||||
0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a,
|
0x6e, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
|
||||||
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x06,
|
||||||
0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d,
|
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67,
|
||||||
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
0x61, 0x22, 0x37, 0x0a, 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74,
|
||||||
0x73, 0x65, 0x12, 0x26, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01,
|
0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
|
||||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||||
0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50,
|
0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x37, 0x0a, 0x0d,
|
||||||
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70,
|
0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a,
|
||||||
0x22, 0x35, 0x0a, 0x06, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70,
|
0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52,
|
0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73,
|
||||||
0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||||
0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65,
|
0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x22, 0x35, 0x0a, 0x06, 0x49,
|
||||||
0x66, 0x69, 0x78, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04,
|
0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12,
|
||||||
0x62, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73,
|
0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f,
|
||||||
0x22, 0x75, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x17,
|
||||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
|
0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69,
|
||||||
0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73, 0x18,
|
||||||
0x06, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18,
|
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x22, 0x75, 0x0a, 0x0b, 0x4c,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73,
|
0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||||
0x69, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f,
|
||||||
0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x6c,
|
||||||
0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45,
|
0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
|
||||||
0x6e, 0x74, 0x72, 0x79, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01,
|
0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18,
|
||||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e,
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||||
0x74, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06,
|
0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6e, 0x74,
|
||||||
0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
|
||||||
0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53,
|
||||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61,
|
||||||
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02,
|
||||||
0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||||
0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,
|
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||||
0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10,
|
0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d,
|
||||||
0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a,
|
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65,
|
||||||
0x09, 0x48, 0x45, 0x41, 0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x03, 0x42, 0x37, 0x5a, 0x35,
|
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54,
|
||||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64,
|
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
|
||||||
0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e,
|
0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06,
|
||||||
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61,
|
0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41, 0x52,
|
||||||
0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
file_internal_machine_api_pb_common_proto_rawDescOnce sync.Once
|
file_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_rawDescData = file_api_pb_common_proto_rawDesc
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_internal_machine_api_pb_common_proto_rawDescGZIP() []byte {
|
func file_api_pb_common_proto_rawDescGZIP() []byte {
|
||||||
file_internal_machine_api_pb_common_proto_rawDescOnce.Do(func() {
|
file_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)
|
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_api_pb_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
var file_internal_machine_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
var file_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_goTypes = []any{
|
||||||
(LogEntry_StreamType)(0), // 0: api.LogEntry.StreamType
|
(LogEntry_StreamType)(0), // 0: api.LogEntry.StreamType
|
||||||
(*Metadata)(nil), // 1: api.Metadata
|
(*Metadata)(nil), // 1: api.Metadata
|
||||||
(*Empty)(nil), // 2: api.Empty
|
(*Empty)(nil), // 2: api.Empty
|
||||||
@@ -625,7 +647,7 @@ var file_internal_machine_api_pb_common_proto_goTypes = []any{
|
|||||||
(*status.Status)(nil), // 9: google.rpc.Status
|
(*status.Status)(nil), // 9: google.rpc.Status
|
||||||
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
(*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
|
9, // 0: api.Metadata.status:type_name -> google.rpc.Status
|
||||||
1, // 1: api.Empty.metadata:type_name -> api.Metadata
|
1, // 1: api.Empty.metadata:type_name -> api.Metadata
|
||||||
2, // 2: api.EmptyResponse.messages:type_name -> api.Empty
|
2, // 2: api.EmptyResponse.messages:type_name -> api.Empty
|
||||||
@@ -640,13 +662,13 @@ var file_internal_machine_api_pb_common_proto_depIdxs = []int32{
|
|||||||
0, // [0:7] is the sub-list for field type_name
|
0, // [0:7] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_internal_machine_api_pb_common_proto_init() }
|
func init() { file_api_pb_common_proto_init() }
|
||||||
func file_internal_machine_api_pb_common_proto_init() {
|
func file_api_pb_common_proto_init() {
|
||||||
if File_internal_machine_api_pb_common_proto != nil {
|
if File_api_pb_common_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
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 {
|
switch v := v.(*Metadata); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -658,7 +680,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
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 {
|
switch v := v.(*Empty); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -670,7 +692,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
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 {
|
switch v := v.(*EmptyResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -682,7 +704,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
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 {
|
switch v := v.(*IP); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -694,7 +716,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
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 {
|
switch v := v.(*IPPort); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -706,7 +728,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
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 {
|
switch v := v.(*IPPrefix); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -718,7 +740,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
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 {
|
switch v := v.(*LogsRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -730,7 +752,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
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 {
|
switch v := v.(*LogEntry); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -747,19 +769,19 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_internal_machine_api_pb_common_proto_rawDesc,
|
RawDescriptor: file_api_pb_common_proto_rawDesc,
|
||||||
NumEnums: 1,
|
NumEnums: 1,
|
||||||
NumMessages: 8,
|
NumMessages: 8,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_internal_machine_api_pb_common_proto_goTypes,
|
GoTypes: file_api_pb_common_proto_goTypes,
|
||||||
DependencyIndexes: file_internal_machine_api_pb_common_proto_depIdxs,
|
DependencyIndexes: file_api_pb_common_proto_depIdxs,
|
||||||
EnumInfos: file_internal_machine_api_pb_common_proto_enumTypes,
|
EnumInfos: file_api_pb_common_proto_enumTypes,
|
||||||
MessageInfos: file_internal_machine_api_pb_common_proto_msgTypes,
|
MessageInfos: file_api_pb_common_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_internal_machine_api_pb_common_proto = out.File
|
File_api_pb_common_proto = out.File
|
||||||
file_internal_machine_api_pb_common_proto_rawDesc = nil
|
file_api_pb_common_proto_rawDesc = nil
|
||||||
file_internal_machine_api_pb_common_proto_goTypes = nil
|
file_api_pb_common_proto_goTypes = nil
|
||||||
file_internal_machine_api_pb_common_proto_depIdxs = nil
|
file_api_pb_common_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,24 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package api;
|
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/rpc/status.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
|
// 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.
|
// about the machine that responded to the request.
|
||||||
message Metadata {
|
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.
|
// 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.
|
|
||||||
|
// error is set if the request to upstream failed. The rest of the response apart from this metadata message
|
||||||
|
// is undefined.
|
||||||
string error = 2;
|
string error = 2;
|
||||||
// error as a gRPC Status message.
|
// error as a gRPC Status message.
|
||||||
google.rpc.Status status = 3;
|
google.rpc.Status status = 3;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,10 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package api;
|
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/empty.proto";
|
||||||
import "internal/machine/api/pb/common.proto";
|
import "api/pb/common.proto";
|
||||||
|
|
||||||
service Docker {
|
service Docker {
|
||||||
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
|
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
|
||||||
@@ -24,6 +24,7 @@ service Docker {
|
|||||||
// Docker auth credentials if necessary.
|
// Docker auth credentials if necessary.
|
||||||
rpc InspectRemoteImage(InspectRemoteImageRequest) returns (InspectRemoteImageResponse);
|
rpc InspectRemoteImage(InspectRemoteImageRequest) returns (InspectRemoteImageResponse);
|
||||||
rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
|
rpc ListImages(ListImagesRequest) returns (ListImagesResponse);
|
||||||
|
rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse);
|
||||||
|
|
||||||
rpc CreateVolume(CreateVolumeRequest) returns (CreateVolumeResponse);
|
rpc CreateVolume(CreateVolumeRequest) returns (CreateVolumeResponse);
|
||||||
rpc ListVolumes(ListVolumesRequest) returns (ListVolumesResponse);
|
rpc ListVolumes(ListVolumesRequest) returns (ListVolumesResponse);
|
||||||
@@ -31,6 +32,8 @@ service Docker {
|
|||||||
|
|
||||||
rpc CreateServiceContainer(CreateServiceContainerRequest) returns (CreateContainerResponse);
|
rpc CreateServiceContainer(CreateServiceContainerRequest) returns (CreateContainerResponse);
|
||||||
rpc InspectServiceContainer(InspectContainerRequest) returns (ServiceContainer);
|
rpc InspectServiceContainer(InspectContainerRequest) returns (ServiceContainer);
|
||||||
|
// UpdateServiceContainer updates the requested properties of a service container in place without recreating it.
|
||||||
|
rpc UpdateServiceContainer(UpdateServiceContainerRequest) returns (google.protobuf.Empty);
|
||||||
rpc ListServiceContainers(ListServiceContainersRequest) returns (ListServiceContainersResponse);
|
rpc ListServiceContainers(ListServiceContainersRequest) returns (ListServiceContainersResponse);
|
||||||
rpc RemoveServiceContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
rpc RemoveServiceContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
||||||
}
|
}
|
||||||
@@ -179,6 +182,40 @@ message ListImagesRequest {
|
|||||||
bytes options = 1;
|
bytes options = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RemoveImageRequest {
|
||||||
|
string id = 1;
|
||||||
|
bool force = 2;
|
||||||
|
bool prune_children = 3;
|
||||||
|
repeated ImagePlatform platforms = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImagePlatform describes an OCI image platform.
|
||||||
|
message ImagePlatform {
|
||||||
|
string architecture = 1;
|
||||||
|
string os = 2;
|
||||||
|
string os_version = 3;
|
||||||
|
repeated string os_features = 4;
|
||||||
|
string variant = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveImageResponse {
|
||||||
|
// Must contain only one repeated messages field to allow broadcasting RemoveImage requests to multiple machines.
|
||||||
|
repeated MachineImageRemoval messages = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MachineImageRemoval {
|
||||||
|
Metadata metadata = 1;
|
||||||
|
repeated ImageRemoval results = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageRemoval reports an image deletion or an untagged image reference.
|
||||||
|
message ImageRemoval {
|
||||||
|
// The image ID of an image that was deleted.
|
||||||
|
string deleted = 1;
|
||||||
|
// The image reference of an image that was untagged.
|
||||||
|
string untagged = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ListImagesResponse {
|
message ListImagesResponse {
|
||||||
// Must contain only one repeated messages field to allow broadcasting ListImages requests to multiple machines.
|
// Must contain only one repeated messages field to allow broadcasting ListImages requests to multiple machines.
|
||||||
repeated MachineImages messages = 1;
|
repeated MachineImages messages = 1;
|
||||||
@@ -246,6 +283,23 @@ message ServiceContainer {
|
|||||||
bytes service_spec = 2;
|
bytes service_spec = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UpdateServiceContainerRequest {
|
||||||
|
string id = 1;
|
||||||
|
// Fields that are not set are left unchanged. CPU and memory limits can be changed but not removed.
|
||||||
|
// CPU limit in nanocores.
|
||||||
|
optional int64 cpu = 2;
|
||||||
|
// Memory limit in bytes.
|
||||||
|
optional int64 memory = 3;
|
||||||
|
// Memory reservation in bytes.
|
||||||
|
optional int64 memory_reservation = 4;
|
||||||
|
// Service labels that replace all current service labels.
|
||||||
|
Labels service_labels = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Labels {
|
||||||
|
map<string, string> labels = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message ListServiceContainersRequest {
|
message ListServiceContainersRequest {
|
||||||
string service_id = 1;
|
string service_id = 1;
|
||||||
// JSON serialised container.ListOptions.
|
// JSON serialised container.ListOptions.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
// - protoc v5.27.3
|
// - protoc v5.27.3
|
||||||
// source: internal/machine/api/pb/docker.proto
|
// source: api/pb/docker.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -32,11 +32,13 @@ const (
|
|||||||
Docker_InspectImage_FullMethodName = "/api.Docker/InspectImage"
|
Docker_InspectImage_FullMethodName = "/api.Docker/InspectImage"
|
||||||
Docker_InspectRemoteImage_FullMethodName = "/api.Docker/InspectRemoteImage"
|
Docker_InspectRemoteImage_FullMethodName = "/api.Docker/InspectRemoteImage"
|
||||||
Docker_ListImages_FullMethodName = "/api.Docker/ListImages"
|
Docker_ListImages_FullMethodName = "/api.Docker/ListImages"
|
||||||
|
Docker_RemoveImage_FullMethodName = "/api.Docker/RemoveImage"
|
||||||
Docker_CreateVolume_FullMethodName = "/api.Docker/CreateVolume"
|
Docker_CreateVolume_FullMethodName = "/api.Docker/CreateVolume"
|
||||||
Docker_ListVolumes_FullMethodName = "/api.Docker/ListVolumes"
|
Docker_ListVolumes_FullMethodName = "/api.Docker/ListVolumes"
|
||||||
Docker_RemoveVolume_FullMethodName = "/api.Docker/RemoveVolume"
|
Docker_RemoveVolume_FullMethodName = "/api.Docker/RemoveVolume"
|
||||||
Docker_CreateServiceContainer_FullMethodName = "/api.Docker/CreateServiceContainer"
|
Docker_CreateServiceContainer_FullMethodName = "/api.Docker/CreateServiceContainer"
|
||||||
Docker_InspectServiceContainer_FullMethodName = "/api.Docker/InspectServiceContainer"
|
Docker_InspectServiceContainer_FullMethodName = "/api.Docker/InspectServiceContainer"
|
||||||
|
Docker_UpdateServiceContainer_FullMethodName = "/api.Docker/UpdateServiceContainer"
|
||||||
Docker_ListServiceContainers_FullMethodName = "/api.Docker/ListServiceContainers"
|
Docker_ListServiceContainers_FullMethodName = "/api.Docker/ListServiceContainers"
|
||||||
Docker_RemoveServiceContainer_FullMethodName = "/api.Docker/RemoveServiceContainer"
|
Docker_RemoveServiceContainer_FullMethodName = "/api.Docker/RemoveServiceContainer"
|
||||||
)
|
)
|
||||||
@@ -59,11 +61,14 @@ type DockerClient interface {
|
|||||||
// Docker auth credentials if necessary.
|
// Docker auth credentials if necessary.
|
||||||
InspectRemoteImage(ctx context.Context, in *InspectRemoteImageRequest, opts ...grpc.CallOption) (*InspectRemoteImageResponse, error)
|
InspectRemoteImage(ctx context.Context, in *InspectRemoteImageRequest, opts ...grpc.CallOption) (*InspectRemoteImageResponse, error)
|
||||||
ListImages(ctx context.Context, in *ListImagesRequest, opts ...grpc.CallOption) (*ListImagesResponse, error)
|
ListImages(ctx context.Context, in *ListImagesRequest, opts ...grpc.CallOption) (*ListImagesResponse, error)
|
||||||
|
RemoveImage(ctx context.Context, in *RemoveImageRequest, opts ...grpc.CallOption) (*RemoveImageResponse, error)
|
||||||
CreateVolume(ctx context.Context, in *CreateVolumeRequest, opts ...grpc.CallOption) (*CreateVolumeResponse, error)
|
CreateVolume(ctx context.Context, in *CreateVolumeRequest, opts ...grpc.CallOption) (*CreateVolumeResponse, error)
|
||||||
ListVolumes(ctx context.Context, in *ListVolumesRequest, opts ...grpc.CallOption) (*ListVolumesResponse, error)
|
ListVolumes(ctx context.Context, in *ListVolumesRequest, opts ...grpc.CallOption) (*ListVolumesResponse, error)
|
||||||
RemoveVolume(ctx context.Context, in *RemoveVolumeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
RemoveVolume(ctx context.Context, in *RemoveVolumeRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
CreateServiceContainer(ctx context.Context, in *CreateServiceContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error)
|
CreateServiceContainer(ctx context.Context, in *CreateServiceContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error)
|
||||||
InspectServiceContainer(ctx context.Context, in *InspectContainerRequest, opts ...grpc.CallOption) (*ServiceContainer, error)
|
InspectServiceContainer(ctx context.Context, in *InspectContainerRequest, opts ...grpc.CallOption) (*ServiceContainer, error)
|
||||||
|
// UpdateServiceContainer updates the requested properties of a service container in place without recreating it.
|
||||||
|
UpdateServiceContainer(ctx context.Context, in *UpdateServiceContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
ListServiceContainers(ctx context.Context, in *ListServiceContainersRequest, opts ...grpc.CallOption) (*ListServiceContainersResponse, error)
|
ListServiceContainers(ctx context.Context, in *ListServiceContainersRequest, opts ...grpc.CallOption) (*ListServiceContainersResponse, error)
|
||||||
RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
RemoveServiceContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
}
|
}
|
||||||
@@ -217,6 +222,16 @@ func (c *dockerClient) ListImages(ctx context.Context, in *ListImagesRequest, op
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *dockerClient) RemoveImage(ctx context.Context, in *RemoveImageRequest, opts ...grpc.CallOption) (*RemoveImageResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(RemoveImageResponse)
|
||||||
|
err := c.cc.Invoke(ctx, Docker_RemoveImage_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *dockerClient) CreateVolume(ctx context.Context, in *CreateVolumeRequest, opts ...grpc.CallOption) (*CreateVolumeResponse, error) {
|
func (c *dockerClient) CreateVolume(ctx context.Context, in *CreateVolumeRequest, opts ...grpc.CallOption) (*CreateVolumeResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(CreateVolumeResponse)
|
out := new(CreateVolumeResponse)
|
||||||
@@ -267,6 +282,16 @@ func (c *dockerClient) InspectServiceContainer(ctx context.Context, in *InspectC
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *dockerClient) UpdateServiceContainer(ctx context.Context, in *UpdateServiceContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, Docker_UpdateServiceContainer_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *dockerClient) ListServiceContainers(ctx context.Context, in *ListServiceContainersRequest, opts ...grpc.CallOption) (*ListServiceContainersResponse, error) {
|
func (c *dockerClient) ListServiceContainers(ctx context.Context, in *ListServiceContainersRequest, opts ...grpc.CallOption) (*ListServiceContainersResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(ListServiceContainersResponse)
|
out := new(ListServiceContainersResponse)
|
||||||
@@ -305,11 +330,14 @@ type DockerServer interface {
|
|||||||
// Docker auth credentials if necessary.
|
// Docker auth credentials if necessary.
|
||||||
InspectRemoteImage(context.Context, *InspectRemoteImageRequest) (*InspectRemoteImageResponse, error)
|
InspectRemoteImage(context.Context, *InspectRemoteImageRequest) (*InspectRemoteImageResponse, error)
|
||||||
ListImages(context.Context, *ListImagesRequest) (*ListImagesResponse, error)
|
ListImages(context.Context, *ListImagesRequest) (*ListImagesResponse, error)
|
||||||
|
RemoveImage(context.Context, *RemoveImageRequest) (*RemoveImageResponse, error)
|
||||||
CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)
|
CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)
|
||||||
ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)
|
ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)
|
||||||
RemoveVolume(context.Context, *RemoveVolumeRequest) (*emptypb.Empty, error)
|
RemoveVolume(context.Context, *RemoveVolumeRequest) (*emptypb.Empty, error)
|
||||||
CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error)
|
CreateServiceContainer(context.Context, *CreateServiceContainerRequest) (*CreateContainerResponse, error)
|
||||||
InspectServiceContainer(context.Context, *InspectContainerRequest) (*ServiceContainer, error)
|
InspectServiceContainer(context.Context, *InspectContainerRequest) (*ServiceContainer, error)
|
||||||
|
// UpdateServiceContainer updates the requested properties of a service container in place without recreating it.
|
||||||
|
UpdateServiceContainer(context.Context, *UpdateServiceContainerRequest) (*emptypb.Empty, error)
|
||||||
ListServiceContainers(context.Context, *ListServiceContainersRequest) (*ListServiceContainersResponse, error)
|
ListServiceContainers(context.Context, *ListServiceContainersRequest) (*ListServiceContainersResponse, error)
|
||||||
RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
RemoveServiceContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
||||||
mustEmbedUnimplementedDockerServer()
|
mustEmbedUnimplementedDockerServer()
|
||||||
@@ -358,6 +386,9 @@ func (UnimplementedDockerServer) InspectRemoteImage(context.Context, *InspectRem
|
|||||||
func (UnimplementedDockerServer) ListImages(context.Context, *ListImagesRequest) (*ListImagesResponse, error) {
|
func (UnimplementedDockerServer) ListImages(context.Context, *ListImagesRequest) (*ListImagesResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListImages not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListImages not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDockerServer) RemoveImage(context.Context, *RemoveImageRequest) (*RemoveImageResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method RemoveImage not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDockerServer) CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error) {
|
func (UnimplementedDockerServer) CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateVolume not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreateVolume not implemented")
|
||||||
}
|
}
|
||||||
@@ -373,6 +404,9 @@ func (UnimplementedDockerServer) CreateServiceContainer(context.Context, *Create
|
|||||||
func (UnimplementedDockerServer) InspectServiceContainer(context.Context, *InspectContainerRequest) (*ServiceContainer, error) {
|
func (UnimplementedDockerServer) InspectServiceContainer(context.Context, *InspectContainerRequest) (*ServiceContainer, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method InspectServiceContainer not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method InspectServiceContainer not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDockerServer) UpdateServiceContainer(context.Context, *UpdateServiceContainerRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method UpdateServiceContainer not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDockerServer) ListServiceContainers(context.Context, *ListServiceContainersRequest) (*ListServiceContainersResponse, error) {
|
func (UnimplementedDockerServer) ListServiceContainers(context.Context, *ListServiceContainersRequest) (*ListServiceContainersResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListServiceContainers not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListServiceContainers not implemented")
|
||||||
}
|
}
|
||||||
@@ -591,6 +625,24 @@ func _Docker_ListImages_Handler(srv interface{}, ctx context.Context, dec func(i
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Docker_RemoveImage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RemoveImageRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DockerServer).RemoveImage(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Docker_RemoveImage_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DockerServer).RemoveImage(ctx, req.(*RemoveImageRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _Docker_CreateVolume_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Docker_CreateVolume_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(CreateVolumeRequest)
|
in := new(CreateVolumeRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -681,6 +733,24 @@ func _Docker_InspectServiceContainer_Handler(srv interface{}, ctx context.Contex
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Docker_UpdateServiceContainer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(UpdateServiceContainerRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DockerServer).UpdateServiceContainer(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Docker_UpdateServiceContainer_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DockerServer).UpdateServiceContainer(ctx, req.(*UpdateServiceContainerRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _Docker_ListServiceContainers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Docker_ListServiceContainers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ListServiceContainersRequest)
|
in := new(ListServiceContainersRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -760,6 +830,10 @@ var Docker_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ListImages",
|
MethodName: "ListImages",
|
||||||
Handler: _Docker_ListImages_Handler,
|
Handler: _Docker_ListImages_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RemoveImage",
|
||||||
|
Handler: _Docker_RemoveImage_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "CreateVolume",
|
MethodName: "CreateVolume",
|
||||||
Handler: _Docker_CreateVolume_Handler,
|
Handler: _Docker_CreateVolume_Handler,
|
||||||
@@ -780,6 +854,10 @@ var Docker_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "InspectServiceContainer",
|
MethodName: "InspectServiceContainer",
|
||||||
Handler: _Docker_InspectServiceContainer_Handler,
|
Handler: _Docker_InspectServiceContainer_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "UpdateServiceContainer",
|
||||||
|
Handler: _Docker_UpdateServiceContainer_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "ListServiceContainers",
|
MethodName: "ListServiceContainers",
|
||||||
Handler: _Docker_ListServiceContainers_Handler,
|
Handler: _Docker_ListServiceContainers_Handler,
|
||||||
@@ -807,5 +885,5 @@ var Docker_ServiceDesc = grpc.ServiceDesc{
|
|||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Metadata: "internal/machine/api/pb/docker.proto",
|
Metadata: "api/pb/docker.proto",
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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 from active members.
|
||||||
|
// Corrosion may satisfy a version by applying its surviving changes or by marking it complete because its changes
|
||||||
|
// have been superseded.
|
||||||
|
//
|
||||||
|
// Waiting normally makes the captured data available on this machine. However, another write may replace some of that
|
||||||
|
// data before it arrives. Corrosion can then complete the older version without transferring the replaced data.
|
||||||
|
// If the replacement is outside the requested versions, this RPC can succeed while the affected data is still missing
|
||||||
|
// or outdated. This can happen during concurrent updates even when all machines are well connected.
|
||||||
|
//
|
||||||
|
// Success does not guarantee an exact snapshot or delivery of every historical value. Callers that require a specific
|
||||||
|
// record or condition should verify it after waiting.
|
||||||
|
//
|
||||||
|
// This RPC observes native replication without initiating synchronisation.
|
||||||
|
// Use the RPC deadline to bound the wait.
|
||||||
|
rpc WaitForStoreVersion(WaitForStoreVersionRequest) returns (google.protobuf.Empty);
|
||||||
|
// UpdateMachine updates the configuration of the machine.
|
||||||
|
rpc UpdateMachine(UpdateMachineRequest) returns (UpdateMachineResponse);
|
||||||
|
// InspectWireGuardNetwork retrieves the current WireGuard network configuration and peer status.
|
||||||
|
rpc InspectWireGuardNetwork(google.protobuf.Empty) returns (InspectWireGuardNetworkResponse);
|
||||||
|
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
|
||||||
|
rpc Reset(ResetRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
|
rpc InspectService(InspectServiceRequest) returns (InspectServiceResponse);
|
||||||
|
|
||||||
|
rpc MachineLogs(LogsRequest) returns (stream LogEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
message MachineInfo {
|
||||||
|
string id = 1;
|
||||||
|
string name = 2;
|
||||||
|
NetworkConfig network = 3;
|
||||||
|
IP public_ip = 4;
|
||||||
|
// Version of the machine daemon (uncloudd).
|
||||||
|
string daemon_version = 7;
|
||||||
|
// Version of the Docker engine running on the machine. Could be empty if unable to get.
|
||||||
|
string docker_version = 8;
|
||||||
|
|
||||||
|
// Operating system hostname.
|
||||||
|
string hostname = 5;
|
||||||
|
// CPU architecture.
|
||||||
|
string arch = 6;
|
||||||
|
// Human-readable operating system name and version, e.g. "Ubuntu 24.04.4 LTS". Empty if cannot be determined.
|
||||||
|
string os_pretty_name = 9;
|
||||||
|
// Kernel release version, e.g. "6.8.0-31-generic".
|
||||||
|
string kernel_version = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NetworkConfig {
|
||||||
|
IPPrefix subnet = 1;
|
||||||
|
IP management_ip = 2;
|
||||||
|
repeated IPPort endpoints = 3;
|
||||||
|
bytes public_key = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateMachineRequest {
|
||||||
|
// Updated machine information. Only the set fields are applied.
|
||||||
|
optional string name = 1;
|
||||||
|
optional IP public_ip = 2;
|
||||||
|
repeated IPPort endpoints = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateMachineResponse {
|
||||||
|
MachineInfo machine = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckPrerequisitesResponse {
|
||||||
|
// Overall status of the checks.
|
||||||
|
bool satisfied = 1;
|
||||||
|
// Error message if not satisfied (empty if all checks pass).
|
||||||
|
string error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitClusterRequest {
|
||||||
|
string machineName = 1;
|
||||||
|
IPPrefix network = 2;
|
||||||
|
|
||||||
|
oneof public_ip_config {
|
||||||
|
IP public_ip = 3;
|
||||||
|
bool public_ip_auto = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional WireGuard endpoints other machines will use to connect to this machine instead of auto-discovered ones.
|
||||||
|
repeated IPPort wireguard_endpoints = 5;
|
||||||
|
// WireGuard listen port for this machine. Uses the default port (51820) if 0 or not set.
|
||||||
|
int32 wireguard_port = 6;
|
||||||
|
// MTU of the WireGuard interface on this machine. The daemon auto-detects the optimal MTU if 0 or not set.
|
||||||
|
int32 wireguard_mtu = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitClusterResponse {
|
||||||
|
MachineInfo machine = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message JoinClusterRequest {
|
||||||
|
MachineInfo machine = 1;
|
||||||
|
repeated MachineInfo other_machines = 3;
|
||||||
|
// WireGuard listen port for this machine. Uses the default port (51820) if 0 or not set.
|
||||||
|
int32 wireguard_port = 5;
|
||||||
|
// MTU of the WireGuard interface on this machine. The daemon auto-detects the optimal MTU if 0 or not set.
|
||||||
|
int32 wireguard_mtu = 7;
|
||||||
|
// Cluster store version this machine must reach before participating.
|
||||||
|
// Per-actor vector (Corrosion actor UUID → minimum required db_version).
|
||||||
|
map<string, uint64> min_store_version = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InspectMachineResponse {
|
||||||
|
// Must contain only one repeated messages field to allow broadcasting InspectMachine requests to multiple machines.
|
||||||
|
repeated MachineDetails machines = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MachineDetails {
|
||||||
|
Metadata metadata = 1;
|
||||||
|
MachineInfo machine = 2;
|
||||||
|
// Round-trip times to other machines in the cluster, keyed by peer machine ID.
|
||||||
|
map<string, RTTStats> rtts = 4;
|
||||||
|
// Store replication progress observed on this machine.
|
||||||
|
// Maps each Corrosion actor UUID to its highest processed database version. Corrosion also counts versions whose
|
||||||
|
// changes were superseded and skipped. Versions at or below a reported version may still be missing or pending
|
||||||
|
// locally.
|
||||||
|
//
|
||||||
|
// Pass this vector to WaitForStoreVersion on another machine to wait for replication through these versions.
|
||||||
|
// See that RPC's data-availability limitations. To combine observations from several machines, take the maximum
|
||||||
|
// for each actor.
|
||||||
|
// Capturing this vector does not wait for replication or prevent further writes.
|
||||||
|
map<string, uint64> store_version = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WaitForStoreVersionRequest {
|
||||||
|
// Minimum database version to reach for each Corrosion actor UUID.
|
||||||
|
// Obtain a vector from InspectMachine. To combine several responses, take the maximum version for each actor.
|
||||||
|
// Actors omitted from this vector and versions above its targets are not awaited. An empty vector requires
|
||||||
|
// no replication. Unknown actors with positive targets remain pending until replication reaches them or the RPC ends.
|
||||||
|
map<string, uint64> min_version = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TokenResponse {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResetRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message Service {
|
||||||
|
string id = 1;
|
||||||
|
string name = 2;
|
||||||
|
string mode = 3;
|
||||||
|
|
||||||
|
message Container {
|
||||||
|
string machine_id = 1;
|
||||||
|
// JSON encoded Docker types.Container.
|
||||||
|
bytes container = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeated Container containers = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InspectServiceRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InspectServiceResponse {
|
||||||
|
Service service = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InspectWireGuardNetworkResponse {
|
||||||
|
string interface_name = 1;
|
||||||
|
bytes public_key = 2;
|
||||||
|
int32 listen_port = 3;
|
||||||
|
repeated WireGuardPeer peers = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WireGuardPeer {
|
||||||
|
bytes public_key = 1;
|
||||||
|
string endpoint = 2;
|
||||||
|
google.protobuf.Timestamp last_handshake_time = 3;
|
||||||
|
int64 receive_bytes = 4;
|
||||||
|
int64 transmit_bytes = 5;
|
||||||
|
repeated string allowed_ips = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RTTStats {
|
||||||
|
google.protobuf.Duration median = 1;
|
||||||
|
google.protobuf.Duration std_dev = 2;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
// - protoc v5.27.3
|
// - protoc v5.27.3
|
||||||
// source: internal/machine/api/pb/machine.proto
|
// source: api/pb/machine.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ const (
|
|||||||
Machine_Token_FullMethodName = "/api.Machine/Token"
|
Machine_Token_FullMethodName = "/api.Machine/Token"
|
||||||
Machine_Inspect_FullMethodName = "/api.Machine/Inspect"
|
Machine_Inspect_FullMethodName = "/api.Machine/Inspect"
|
||||||
Machine_InspectMachine_FullMethodName = "/api.Machine/InspectMachine"
|
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_InspectWireGuardNetwork_FullMethodName = "/api.Machine/InspectWireGuardNetwork"
|
||||||
Machine_Reset_FullMethodName = "/api.Machine/Reset"
|
Machine_Reset_FullMethodName = "/api.Machine/Reset"
|
||||||
Machine_InspectService_FullMethodName = "/api.Machine/InspectService"
|
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)
|
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 retrieves detailed information about the machine. Supports broadcasting to multiple machines.
|
||||||
InspectMachine(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InspectMachineResponse, error)
|
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 from active members.
|
||||||
|
// Corrosion may satisfy a version by applying its surviving changes or by marking it complete because its changes
|
||||||
|
// have been superseded.
|
||||||
|
//
|
||||||
|
// Waiting normally makes the captured data available on this machine. However, another write may replace some of that
|
||||||
|
// data before it arrives. Corrosion can then complete the older version without transferring the replaced data.
|
||||||
|
// If the replacement is outside the requested versions, this RPC can succeed while the affected data is still missing
|
||||||
|
// or outdated. This can happen during concurrent updates even when all machines are well connected.
|
||||||
|
//
|
||||||
|
// Success does not guarantee an exact snapshot or delivery of every historical value. Callers that require a specific
|
||||||
|
// record or condition should verify it after waiting.
|
||||||
|
//
|
||||||
|
// This RPC observes native replication without initiating synchronisation.
|
||||||
|
// Use the RPC deadline to bound the wait.
|
||||||
|
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 retrieves the current WireGuard network configuration and peer status.
|
||||||
InspectWireGuardNetwork(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InspectWireGuardNetworkResponse, error)
|
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.
|
// 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
|
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) {
|
func (c *machineClient) InspectWireGuardNetwork(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InspectWireGuardNetworkResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(InspectWireGuardNetworkResponse)
|
out := new(InspectWireGuardNetworkResponse)
|
||||||
@@ -183,6 +223,24 @@ type MachineServer interface {
|
|||||||
Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error)
|
Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error)
|
||||||
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
|
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
|
||||||
InspectMachine(context.Context, *emptypb.Empty) (*InspectMachineResponse, error)
|
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 from active members.
|
||||||
|
// Corrosion may satisfy a version by applying its surviving changes or by marking it complete because its changes
|
||||||
|
// have been superseded.
|
||||||
|
//
|
||||||
|
// Waiting normally makes the captured data available on this machine. However, another write may replace some of that
|
||||||
|
// data before it arrives. Corrosion can then complete the older version without transferring the replaced data.
|
||||||
|
// If the replacement is outside the requested versions, this RPC can succeed while the affected data is still missing
|
||||||
|
// or outdated. This can happen during concurrent updates even when all machines are well connected.
|
||||||
|
//
|
||||||
|
// Success does not guarantee an exact snapshot or delivery of every historical value. Callers that require a specific
|
||||||
|
// record or condition should verify it after waiting.
|
||||||
|
//
|
||||||
|
// This RPC observes native replication without initiating synchronisation.
|
||||||
|
// Use the RPC deadline to bound the wait.
|
||||||
|
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 retrieves the current WireGuard network configuration and peer status.
|
||||||
InspectWireGuardNetwork(context.Context, *emptypb.Empty) (*InspectWireGuardNetworkResponse, error)
|
InspectWireGuardNetwork(context.Context, *emptypb.Empty) (*InspectWireGuardNetworkResponse, error)
|
||||||
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
|
// 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) {
|
func (UnimplementedMachineServer) InspectMachine(context.Context, *emptypb.Empty) (*InspectMachineResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method InspectMachine not implemented")
|
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) {
|
func (UnimplementedMachineServer) InspectWireGuardNetwork(context.Context, *emptypb.Empty) (*InspectWireGuardNetworkResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method InspectWireGuardNetwork not implemented")
|
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)
|
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) {
|
func _Machine_InspectWireGuardNetwork_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(emptypb.Empty)
|
in := new(emptypb.Empty)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -454,6 +554,14 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "InspectMachine",
|
MethodName: "InspectMachine",
|
||||||
Handler: _Machine_InspectMachine_Handler,
|
Handler: _Machine_InspectMachine_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "WaitForStoreVersion",
|
||||||
|
Handler: _Machine_WaitForStoreVersion_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "UpdateMachine",
|
||||||
|
Handler: _Machine_UpdateMachine_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "InspectWireGuardNetwork",
|
MethodName: "InspectWireGuardNetwork",
|
||||||
Handler: _Machine_InspectWireGuardNetwork_Handler,
|
Handler: _Machine_InspectWireGuardNetwork_Handler,
|
||||||
@@ -474,5 +582,5 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
|||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Metadata: "internal/machine/api/pb/machine.proto",
|
Metadata: "api/pb/machine.proto",
|
||||||
}
|
}
|
||||||
Vendored
@@ -0,0 +1,4 @@
|
|||||||
|
_ _ ___
|
||||||
|
| | | |/ __|
|
||||||
|
| |_| | (__
|
||||||
|
\__,_|\___|
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package cert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/internal/version"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listOptions struct {
|
||||||
|
machine string
|
||||||
|
output string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListCommand() *cobra.Command {
|
||||||
|
opts := listOptions{}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Aliases: []string{"list"},
|
||||||
|
Short: "List certificates in cluster storage for Caddy.",
|
||||||
|
Long: `List certificates stored in the Uncloud cluster storage for Caddy.
|
||||||
|
|
||||||
|
Caddy uses cluster storage by default. Certificates don't appear here if Caddy uses local storage,
|
||||||
|
for example, if it runs with a custom global config without 'storage uncloud' or was deployed with
|
||||||
|
uc v0.21.0 or earlier and not upgraded.
|
||||||
|
|
||||||
|
This inventory does not check whether Caddy currently serves or trusts a certificate.
|
||||||
|
|
||||||
|
See ` + version.DocsURL + `/concepts/ingress/cluster-storage-for-caddy for details.`,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return list(cmd.Context(), uncli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||||
|
"Name or ID of the machine to read certificate storage from. (default is connected machine)")
|
||||||
|
cmd.Flags().StringVarP(&opts.output, "output", "o", "",
|
||||||
|
"Output format: 'json' or empty for a human-readable table.")
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
||||||
|
if opts.output != "" && opts.output != "json" {
|
||||||
|
return fmt.Errorf("unsupported output format '%s' (supported: json)", opts.output)
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterClient, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer clusterClient.Close()
|
||||||
|
|
||||||
|
certs, listErr := clusterClient.Caddy.ListCertificates(ctx,
|
||||||
|
client.CaddyListCertificatesOptions{Machine: opts.machine})
|
||||||
|
if len(certs) > 0 || listErr == nil {
|
||||||
|
if err = printCertificates(os.Stdout, certs, opts.output); err != nil {
|
||||||
|
return fmt.Errorf("print Caddy certificates: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if listErr != nil {
|
||||||
|
if len(certs) > 0 {
|
||||||
|
return fmt.Errorf("certificate list is partial: %w", listErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("list Caddy certificates: %w", listErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printCertificates(out io.Writer, certs []api.IssuedCertificate, output string) error {
|
||||||
|
type certificate struct {
|
||||||
|
api.IssuedCertificate
|
||||||
|
Fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]certificate, 0, len(certs))
|
||||||
|
for _, cert := range certs {
|
||||||
|
fingerprint := cert.Fingerprint()
|
||||||
|
items = append(items, certificate{
|
||||||
|
IssuedCertificate: cert,
|
||||||
|
Fingerprint: hex.EncodeToString(fingerprint[:]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
slices.SortFunc(items, func(a, b certificate) int {
|
||||||
|
if cmp := strings.Compare(a.SAN, b.SAN); cmp != 0 {
|
||||||
|
return cmp
|
||||||
|
}
|
||||||
|
if cmp := a.Chain[0].NotAfter.Compare(b.Chain[0].NotAfter); cmp != 0 {
|
||||||
|
return cmp
|
||||||
|
}
|
||||||
|
return strings.Compare(a.Fingerprint, b.Fingerprint)
|
||||||
|
})
|
||||||
|
|
||||||
|
if output == "json" {
|
||||||
|
encoder := json.NewEncoder(out)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
if err := encoder.Encode(items); err != nil {
|
||||||
|
return fmt.Errorf("marshal certificates: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
_, err := fmt.Fprintln(out, "No Caddy certificates found in cluster storage.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tui.NewTable()
|
||||||
|
t.Headers("ID", "NAME", "ISSUER", "EXPIRES")
|
||||||
|
for _, item := range items {
|
||||||
|
t.Row(
|
||||||
|
item.Fingerprint[:12],
|
||||||
|
item.SAN,
|
||||||
|
formatIssuer(item.IssuerData),
|
||||||
|
formatExpiry(item.Chain[0].NotAfter),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_, err := lipgloss.Fprintln(out, t)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatIssuer(data api.CertificateIssuerData) string {
|
||||||
|
if data.ACME == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data.ACME.CA {
|
||||||
|
case certmagic.LetsEncryptProductionCA:
|
||||||
|
return "Let's Encrypt"
|
||||||
|
case certmagic.LetsEncryptStagingCA:
|
||||||
|
return "Let's Encrypt (staging)"
|
||||||
|
case certmagic.ZeroSSLProductionCA:
|
||||||
|
return "ZeroSSL"
|
||||||
|
case certmagic.GoogleTrustProductionCA:
|
||||||
|
return "Google Trust Services"
|
||||||
|
case certmagic.GoogleTrustStagingCA:
|
||||||
|
return "Google Trust Services (staging)"
|
||||||
|
default:
|
||||||
|
return data.ACME.CA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatExpiry(expires time.Time) string {
|
||||||
|
delta := expires.Sub(time.Now())
|
||||||
|
if delta <= 0 {
|
||||||
|
return fmt.Sprintf("%s (expired %s ago)", expires.UTC().Format(time.DateOnly),
|
||||||
|
strings.ToLower(units.HumanDuration(-delta)))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (%s)", expires.UTC().Format(time.DateOnly),
|
||||||
|
strings.ToLower(units.HumanDuration(delta)))
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package cert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintCertificates(t *testing.T) {
|
||||||
|
now := time.Now().UTC().Truncate(time.Hour)
|
||||||
|
certs := []api.IssuedCertificate{
|
||||||
|
testIssuedCertificate("z.example.com", "z", now.Add(48*time.Hour), certmagic.LetsEncryptProductionCA),
|
||||||
|
testIssuedCertificate("a.example.com", "old", now.Add(-48*time.Hour), certmagic.LetsEncryptStagingCA),
|
||||||
|
testIssuedCertificate("a.example.com", "new", now.Add(72*time.Hour), "https://ca.example/directory"),
|
||||||
|
testIssuedCertificate("unknown.example.com", "unknown", now.Add(96*time.Hour), ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
var table bytes.Buffer
|
||||||
|
require.NoError(t, printCertificates(&table, certs, ""))
|
||||||
|
output := table.String()
|
||||||
|
assert.Contains(t, output, "ID")
|
||||||
|
assert.Contains(t, output, "NAME")
|
||||||
|
assert.Contains(t, output, "ISSUER")
|
||||||
|
assert.Contains(t, output, "EXPIRES")
|
||||||
|
assert.Contains(t, output, now.Add(-48*time.Hour).Format(time.DateOnly)+" (expired ")
|
||||||
|
assert.Contains(t, output, now.Add(48*time.Hour).Format(time.DateOnly)+" (")
|
||||||
|
assert.Contains(t, output, "Let's Encrypt (staging)")
|
||||||
|
assert.Contains(t, output, "https://ca.example/directory")
|
||||||
|
assert.Less(t, strings.Index(output, now.Add(-48*time.Hour).Format(time.DateOnly)),
|
||||||
|
strings.Index(output, now.Add(72*time.Hour).Format(time.DateOnly)))
|
||||||
|
assert.Less(t, strings.Index(output, "a.example.com"), strings.Index(output, "z.example.com"))
|
||||||
|
assert.Contains(t, output, testFingerprint("old")[:12])
|
||||||
|
|
||||||
|
var encoded bytes.Buffer
|
||||||
|
require.NoError(t, printCertificates(&encoded, certs, "json"))
|
||||||
|
var items []struct {
|
||||||
|
api.IssuedCertificate
|
||||||
|
Fingerprint string
|
||||||
|
}
|
||||||
|
require.NoError(t, json.Unmarshal(encoded.Bytes(), &items))
|
||||||
|
require.Len(t, items, 4)
|
||||||
|
assert.Equal(t, testFingerprint("old"), items[0].Fingerprint)
|
||||||
|
assert.Equal(t, "a.example.com", items[0].SAN)
|
||||||
|
require.Len(t, items[0].Chain, 1)
|
||||||
|
assert.Equal(t, []byte("old"), items[0].Chain[0].Raw)
|
||||||
|
assert.Equal(t, now.Add(-48*time.Hour), items[0].Chain[0].NotAfter)
|
||||||
|
require.NotNil(t, items[0].IssuerData.ACME)
|
||||||
|
assert.Equal(t, certmagic.LetsEncryptStagingCA, items[0].IssuerData.ACME.CA)
|
||||||
|
assert.Equal(t, testFingerprint("new"), items[1].Fingerprint)
|
||||||
|
require.NotNil(t, items[1].IssuerData.ACME)
|
||||||
|
assert.Equal(t, "https://ca.example/directory", items[1].IssuerData.ACME.CA)
|
||||||
|
assert.Nil(t, items[2].IssuerData.ACME)
|
||||||
|
assert.Equal(t, "z.example.com", items[3].SAN)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintCertificates_Empty(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
output string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{output: "", want: "No Caddy certificates found in cluster storage.\n"},
|
||||||
|
{output: "json", want: "[]\n"},
|
||||||
|
} {
|
||||||
|
var out bytes.Buffer
|
||||||
|
require.NoError(t, printCertificates(&out, nil, tt.output))
|
||||||
|
assert.Equal(t, tt.want, out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatIssuer(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
ca string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "production", ca: certmagic.LetsEncryptProductionCA, want: "Let's Encrypt"},
|
||||||
|
{name: "staging", ca: certmagic.LetsEncryptStagingCA, want: "Let's Encrypt (staging)"},
|
||||||
|
{name: "ZeroSSL", ca: certmagic.ZeroSSLProductionCA, want: "ZeroSSL"},
|
||||||
|
{name: "Google Trust Services", ca: certmagic.GoogleTrustProductionCA, want: "Google Trust Services"},
|
||||||
|
{name: "Google Trust Services staging", ca: certmagic.GoogleTrustStagingCA, want: "Google Trust Services (staging)"},
|
||||||
|
{name: "other", ca: "https://ca.example/directory", want: "https://ca.example/directory"},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, formatIssuer(api.CertificateIssuerData{
|
||||||
|
ACME: &api.ACMEIssuerData{CA: tt.ca},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "unknown", formatIssuer(api.CertificateIssuerData{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatExpiry_UTC(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
brisbane := time.FixedZone("AEST", 10*60*60)
|
||||||
|
expires := time.Date(now.Year(), now.Month(), now.Day()+3, 3, 0, 0, 0, brisbane)
|
||||||
|
assert.True(t, strings.HasPrefix(formatExpiry(expires), expires.UTC().Format(time.DateOnly)+" ("))
|
||||||
|
assert.NotEqual(t, expires.Format(time.DateOnly), expires.UTC().Format(time.DateOnly))
|
||||||
|
expired := now.Add(-2 * time.Hour)
|
||||||
|
assert.True(t, strings.HasPrefix(formatExpiry(expired), expired.UTC().Format(time.DateOnly)+" (expired "))
|
||||||
|
assert.Contains(t, formatExpiry(expired), " ago)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIssuedCertificate(name, raw string, expires time.Time, ca string) api.IssuedCertificate {
|
||||||
|
cert := api.IssuedCertificate{
|
||||||
|
SAN: name,
|
||||||
|
Chain: []*x509.Certificate{{Raw: []byte(raw), NotAfter: expires}},
|
||||||
|
}
|
||||||
|
if ca != "" {
|
||||||
|
cert.IssuerData.ACME = &api.ACMEIssuerData{CA: ca}
|
||||||
|
}
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFingerprint(raw string) string {
|
||||||
|
sum := sha256.Sum256([]byte(raw))
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package cert
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
func NewRootCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "cert",
|
||||||
|
Short: "Inspect certificates in cluster storage for Caddy.",
|
||||||
|
}
|
||||||
|
cmd.AddCommand(NewListCommand())
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
@@ -3,17 +3,18 @@ package caddy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/alecthomas/chroma/v2/quick"
|
"github.com/alecthomas/chroma/v2/quick"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configOptions struct {
|
type configOptions struct {
|
||||||
machine string
|
machine string
|
||||||
noColor bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigCommand() *cobra.Command {
|
func NewConfigCommand() *cobra.Command {
|
||||||
@@ -31,8 +32,6 @@ func NewConfigCommand() *cobra.Command {
|
|||||||
|
|
||||||
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||||
"Name or ID of the machine to get the configuration from. (default is connected machine)")
|
"Name or ID of the machine to get the configuration from. (default is connected machine)")
|
||||||
cmd.Flags().BoolVar(&opts.noColor, "no-color", false,
|
|
||||||
"Disable syntax highlighting for the output.")
|
|
||||||
|
|
||||||
completion.MachinesFlag(cmd)
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
@@ -46,27 +45,20 @@ func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
|
|||||||
}
|
}
|
||||||
defer clusterClient.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
if opts.machine != "" {
|
config, err := clusterClient.Caddy.Config(ctx, client.CaddyConfigOptions{Machine: opts.machine})
|
||||||
// If a specific machine is requested, use it to get the Caddy configuration.
|
|
||||||
ctx, _, err = clusterClient.ProxyMachinesContext(ctx, []string{opts.machine})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := clusterClient.Caddy.GetConfig(ctx, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get Caddy config: %w", err)
|
return fmt.Errorf("get Caddy config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the Caddyfile with syntax highlighting.
|
// Print the Caddyfile with syntax highlighting.
|
||||||
if opts.noColor {
|
if err = quick.Highlight(lipgloss.Writer, config.Caddyfile, "caddy", "terminal256", "monokai"); err != nil {
|
||||||
fmt.Print(config.Caddyfile)
|
// If highlighting fails, fall back to plain output.
|
||||||
} else {
|
lipgloss.Print(config.Caddyfile)
|
||||||
if err = quick.Highlight(os.Stdout, config.Caddyfile, "caddy", "terminal256", "monokai"); err != nil {
|
}
|
||||||
// If highlighting fails, fall back to plain output.
|
|
||||||
fmt.Print(config.Caddyfile)
|
if config.LastReconciliationError != "" {
|
||||||
}
|
tui.PrintWarning(fmt.Sprintf("last Caddy config load failed: %s\nShowing the last saved Caddyfile.",
|
||||||
|
config.LastReconciliationError))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
package caddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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/version"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type deployOptions struct {
|
||||||
|
caddyfile string
|
||||||
|
image string
|
||||||
|
machines []string
|
||||||
|
|
||||||
|
caddyfileChanged bool
|
||||||
|
imageChanged bool
|
||||||
|
machinesChanged bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeployCommand() *cobra.Command {
|
||||||
|
opts := deployOptions{}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "deploy",
|
||||||
|
Short: "Deploy or upgrade Caddy reverse proxy across all machines in the cluster.",
|
||||||
|
Long: `Deploy or upgrade Caddy reverse proxy across all machines in the cluster.
|
||||||
|
A rolling update is performed when updating existing containers to minimise disruption.
|
||||||
|
|
||||||
|
By default, this deploys the latest version of the Uncloud Caddy image with a global config that enables cluster
|
||||||
|
storage for TLS certificates. See https://github.com/unlabs-dev/caddy-uncloud for more details.
|
||||||
|
|
||||||
|
Values set with --image, --caddyfile, and --machine are kept for later deployments. Pass an empty value to reset
|
||||||
|
a flag to its default.
|
||||||
|
|
||||||
|
If this command didn't deploy the current caddy service, for example, it was deployed with 'uc deploy'
|
||||||
|
from a Compose file or an older version of uc, the deployment replaces its configuration.
|
||||||
|
|
||||||
|
Requires Uncloud daemon ` + client.MinCaddyDaemonVersion + ` or newer on all machines.`,
|
||||||
|
Example: ` # Deploy Caddy or upgrade it to the latest version of the Uncloud Caddy image.
|
||||||
|
uc caddy deploy
|
||||||
|
|
||||||
|
# Deploy a custom image and keep it for later deployments.
|
||||||
|
uc caddy deploy --image ghcr.io/unlabs-dev/caddy-uncloud:0.1.3
|
||||||
|
|
||||||
|
# Go back to the latest version of the Uncloud Caddy image.
|
||||||
|
uc caddy deploy --image ""
|
||||||
|
|
||||||
|
# Deploy a custom image build and a custom global Caddy config and keep both for later deployments.
|
||||||
|
uc caddy deploy --image myorg/caddy-uncloud:latest --caddyfile global.Caddyfile
|
||||||
|
|
||||||
|
# Go back to the default global Caddy config but keep the custom image.
|
||||||
|
uc caddy deploy --caddyfile ""
|
||||||
|
|
||||||
|
# Deploy only to specific machines and keep them for later deployments.
|
||||||
|
uc caddy deploy --machine machine1,machine2
|
||||||
|
|
||||||
|
# Go back to deploying to all machines.
|
||||||
|
uc caddy deploy --machine ""`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
|
opts.caddyfileChanged = cmd.Flag("caddyfile").Changed
|
||||||
|
opts.imageChanged = cmd.Flag("image").Changed
|
||||||
|
opts.machinesChanged = cmd.Flag("machine").Changed
|
||||||
|
|
||||||
|
return runDeploy(cmd.Context(), uncli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&opts.caddyfile, "caddyfile", "",
|
||||||
|
"Path to a custom global Caddy config (Caddyfile) that will be prepended to the auto-generated Caddy config.\n"+
|
||||||
|
"An empty value resets it to the default config that enables cluster storage.\n"+
|
||||||
|
"(default is the current custom config or the default config)")
|
||||||
|
cmd.Flags().StringVar(&opts.image, "image", "",
|
||||||
|
"Caddy Docker image to deploy. It must include the Uncloud storage module if the Caddy config uses it.\n"+
|
||||||
|
"An empty value resets it to the latest version of the Uncloud Caddy image.\n"+
|
||||||
|
"(default is the current custom image or "+client.CaddyImage+":LATEST_VERSION)")
|
||||||
|
cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil,
|
||||||
|
"Machine names or IDs to deploy to. Can be specified multiple times or as a comma-separated list.\n"+
|
||||||
|
"An empty value resets it to all machines.\n"+
|
||||||
|
"(default is the current machines or all machines)")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
||||||
|
config := ""
|
||||||
|
if opts.caddyfile != "" {
|
||||||
|
data, err := os.ReadFile(opts.caddyfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read Caddyfile: %w", err)
|
||||||
|
}
|
||||||
|
config = strings.TrimSpace(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterClient, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer clusterClient.Close()
|
||||||
|
|
||||||
|
if err = clusterClient.Caddy.CheckDaemonVersions(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, api.ErrNotFound) {
|
||||||
|
return fmt.Errorf("inspect caddy service: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println(tui.Faint.Render("service: ") + tui.NameStyle.Render(client.CaddyServiceName) +
|
||||||
|
tui.Faint.Render(" (not running)"))
|
||||||
|
} else {
|
||||||
|
fmt.Println(tui.Faint.Render("service: ") + tui.NameStyle.Render(svc.Name) +
|
||||||
|
tui.Faint.Render(" ("+svc.Mode+" mode)"))
|
||||||
|
|
||||||
|
// Collect unique images of all containers in the running caddy service.
|
||||||
|
images := make(map[string]struct{}, len(svc.Containers))
|
||||||
|
for _, c := range svc.Containers {
|
||||||
|
images[c.Container.Config.Image] = struct{}{}
|
||||||
|
}
|
||||||
|
currentImages := slices.Collect(maps.Keys(images))
|
||||||
|
|
||||||
|
if len(currentImages) > 1 {
|
||||||
|
formattedImages := make([]string, len(currentImages))
|
||||||
|
for i, img := range currentImages {
|
||||||
|
formattedImages[i] = tui.FormatImage(img, tui.NoStyle)
|
||||||
|
}
|
||||||
|
fmt.Println(tui.Faint.Render("current images (multiple versions detected): ") +
|
||||||
|
strings.Join(formattedImages, tui.Faint.Render(", ")))
|
||||||
|
} else {
|
||||||
|
fmt.Println(tui.Faint.Render("current image: ") + tui.FormatImage(currentImages[0], tui.NoStyle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current := svc.Spec()
|
||||||
|
deployOpts := resolveDeploymentOptions(current, opts, config)
|
||||||
|
configChanged := deployOpts.ConfigOrDefault() != current.CaddyConfig()
|
||||||
|
|
||||||
|
if svc.ID != "" && configChanged {
|
||||||
|
printCaddyConfig("current global Caddy config:", current.CaddyConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Preparing a deployment plan...")
|
||||||
|
|
||||||
|
d, err := clusterClient.Caddy.NewDeployment(ctx, deployOpts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
|
}
|
||||||
|
target := d.Spec
|
||||||
|
|
||||||
|
// The Uncloud Caddy image resolves to the latest version allowed by this CLI, which is older than the running image
|
||||||
|
// if a newer CLI deployed it. Continuing would downgrade the image and replace its default config.
|
||||||
|
if deployOpts.Image == "" && client.IsNewerCaddyImage(current.Container.Image, target.Container.Image) {
|
||||||
|
return fmt.Errorf("caddy service runs image '%s' which is newer than the latest version '%s' supported by "+
|
||||||
|
"this uc version: upgrade uc or specify the image with --image",
|
||||||
|
current.Container.Image, target.Container.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageNote := " (latest)"
|
||||||
|
if deployOpts.Image != "" {
|
||||||
|
imageNote = " (custom)"
|
||||||
|
if !opts.imageChanged {
|
||||||
|
imageNote = " (custom, use --image \"\" for the latest version)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(tui.Faint.Render("target image: ") + tui.FormatImage(target.Container.Image, tui.Green) +
|
||||||
|
tui.Faint.Render(imageNote))
|
||||||
|
|
||||||
|
if configChanged {
|
||||||
|
if deployOpts.Config != nil {
|
||||||
|
// The user provided this config so there is no need to print its content.
|
||||||
|
fmt.Println(tui.Faint.Render("target global Caddy config: ") + opts.caddyfile)
|
||||||
|
} else {
|
||||||
|
printCaddyConfig("target global Caddy config: (default)", deployOpts.ConfigOrDefault())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plan, err := d.Plan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("plan caddy deployment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(plan.Operations) == 0 {
|
||||||
|
fmt.Printf("%s service is up to date.\n", client.CaddyServiceName)
|
||||||
|
} else {
|
||||||
|
if svc.ID == "" {
|
||||||
|
if len(deployOpts.Placement.Machines) > 0 {
|
||||||
|
fmt.Println("This will run a Caddy container on each selected machine.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("This will run a Caddy container on each machine.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(tui.Bold.Underline(true).Render("Deployment plan"))
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
directConn := uncli.DirectConnection()
|
||||||
|
contextName := uncli.ContextOverrideOrCurrent()
|
||||||
|
deployTarget := ""
|
||||||
|
if directConn != "" {
|
||||||
|
deployTarget = directConn
|
||||||
|
fmt.Println(tui.Faint.Render("connection: ") + tui.NameStyle.Render(directConn))
|
||||||
|
fmt.Println()
|
||||||
|
} else if contextName != "" && len(uncli.Config.Contexts) > 1 {
|
||||||
|
// Only show context if there's more than one to avoid unnecessary clutter.
|
||||||
|
deployTarget = contextName
|
||||||
|
fmt.Println(tui.Faint.Render("context: ") + tui.NameStyle.Render(contextName))
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(plan.Format())
|
||||||
|
|
||||||
|
summary := plan.FormatSummary()
|
||||||
|
fmt.Println(tui.Faint.Render(strings.Repeat("─", lipgloss.Width(summary))))
|
||||||
|
fmt.Println(summary)
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
warnings := deploymentWarnings(current, target, opts)
|
||||||
|
for _, warning := range warnings {
|
||||||
|
tui.PrintWarning(warning)
|
||||||
|
}
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
title := "Proceed with deployment?"
|
||||||
|
// Include the direct connection or context name in the confirmation prompt to avoid accidentally
|
||||||
|
// deploying to the wrong cluster.
|
||||||
|
if deployTarget != "" {
|
||||||
|
isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
|
||||||
|
confirmStyle := tui.ThemeConfirm().Theme(isDark).Focused.Title
|
||||||
|
title = "Proceed with deployment to " + tui.NameStyle.Render(deployTarget) + confirmStyle.Render("?")
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmed, err := tui.Confirm(title)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("confirm deployment: %w", err)
|
||||||
|
}
|
||||||
|
if !confirmed {
|
||||||
|
return cli.Cancelled("Caddy deploy cancelled. No changes were made.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
|
if _, err = d.Run(ctx); err != nil {
|
||||||
|
return fmt.Errorf("deploy caddy: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, uncli.ProgressOut(), fmt.Sprintf("Deploying service %s (%s mode)", d.Spec.Name, d.Spec.Mode))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
return UpdateDomainRecords(ctx, clusterClient, uncli.ProgressOut())
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveDeploymentOptions returns the deployment options for the flags and the current service spec, which is zero if
|
||||||
|
// the service doesn't exist. Each flag works on its own: a specified flag overrides the value and an empty one resets
|
||||||
|
// it to the default. A flag that isn't specified keeps the explicitly set value.
|
||||||
|
func resolveDeploymentOptions(
|
||||||
|
current api.ServiceSpec, opts deployOptions, config string,
|
||||||
|
) client.CaddyDeploymentOptions {
|
||||||
|
var resolved client.CaddyDeploymentOptions
|
||||||
|
|
||||||
|
if opts.imageChanged {
|
||||||
|
resolved.Image = opts.image
|
||||||
|
} else if _, custom := current.Labels[api.LabelCaddyImageCustom]; custom {
|
||||||
|
resolved.Image = current.Container.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.caddyfileChanged {
|
||||||
|
if opts.caddyfile != "" {
|
||||||
|
resolved.Config = new(config)
|
||||||
|
}
|
||||||
|
} else if _, custom := current.Labels[api.LabelCaddyConfigCustom]; custom {
|
||||||
|
resolved.Config = new(current.CaddyConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.machinesChanged {
|
||||||
|
resolved.Placement.Machines = cli.ExpandCommaSeparatedValues(opts.machines)
|
||||||
|
} else if _, managed := current.Labels[api.LabelCaddyManaged]; managed {
|
||||||
|
resolved.Placement = current.Placement
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
// deploymentWarnings returns the consequences of deploying the target spec over the current one, which is zero if
|
||||||
|
// the service doesn't exist.
|
||||||
|
func deploymentWarnings(current, target api.ServiceSpec, opts deployOptions) []string {
|
||||||
|
var warnings []string
|
||||||
|
exists := current.Name != ""
|
||||||
|
|
||||||
|
if _, managed := current.Labels[api.LabelCaddyManaged]; exists && !managed {
|
||||||
|
warnings = append(warnings, "'uc caddy deploy' doesn't manage this caddy service yet, so this deployment "+
|
||||||
|
"replaces its configuration, including any custom global Caddy config.\n"+
|
||||||
|
"If you manage it with a Compose file, cancel and deploy with 'uc deploy' instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStorage := client.CaddyStorageModule(current.CaddyConfig())
|
||||||
|
targetStorage := client.CaddyStorageModule(target.CaddyConfig())
|
||||||
|
if exists && currentStorage != targetStorage {
|
||||||
|
warning := fmt.Sprintf("Caddy certificate storage changes from %s to %s.\n"+
|
||||||
|
"Caddy doesn't automatically migrate certificates, so all of them will be reissued and HTTPS may fail "+
|
||||||
|
"for those domains until that finishes.\n"+
|
||||||
|
"To migrate them first, see %s/concepts/ingress/cluster-storage-for-caddy",
|
||||||
|
caddyStorageName(currentStorage), caddyStorageName(targetStorage), version.DocsURL)
|
||||||
|
if !opts.caddyfileChanged {
|
||||||
|
warning += "\nTo keep the current storage, specify a custom Caddyfile that uses it with --caddyfile."
|
||||||
|
}
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageOrConfigChanged := current.Container.Image != target.Container.Image ||
|
||||||
|
current.CaddyConfig() != target.CaddyConfig()
|
||||||
|
if targetStorage == client.CaddyStorageUncloud && imageOrConfigChanged &&
|
||||||
|
!client.IsDefaultCaddyImage(target.Container.Image) {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("Image '%s' must include the Uncloud storage module to use "+
|
||||||
|
"'storage uncloud'. Otherwise, Caddy won't start or apply the new config.\n"+
|
||||||
|
"Check 'uc caddy config' after the deployment for errors.", target.Container.Image))
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
// caddyStorageName returns a user-friendly name of the Caddy storage module.
|
||||||
|
func caddyStorageName(module string) string {
|
||||||
|
switch module {
|
||||||
|
case client.CaddyStorageFileSystem:
|
||||||
|
return "local"
|
||||||
|
case client.CaddyStorageUncloud:
|
||||||
|
return "cluster"
|
||||||
|
default:
|
||||||
|
return "'" + module + "'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printCaddyConfig prints the title and the indented config or "none" if it's empty.
|
||||||
|
func printCaddyConfig(title, config string) {
|
||||||
|
if config == "" {
|
||||||
|
lipgloss.Println(tui.Faint.Render(title) + " none")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lipgloss.Println(tui.Faint.Render(title))
|
||||||
|
fmt.Println(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, progressOut *streams.Out) error {
|
||||||
|
domain, err := clusterClient.GetDomain(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrNotFound) {
|
||||||
|
fmt.Println("Skipping DNS records update as no cluster domain is reserved (see 'uc dns').")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("get cluster domain: %w", err)
|
||||||
|
}
|
||||||
|
if !domain.Reserved {
|
||||||
|
fmt.Printf("Skipping DNS records update as cluster domain '%s' is managed externally.\n", domain.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Updating cluster domain records in Uncloud DNS to point to machines running caddy service...")
|
||||||
|
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
||||||
|
|
||||||
|
var records []*pb.DNSRecord
|
||||||
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
records, err = clusterClient.CreateIngressRecords(ctx, client.CaddyServiceName)
|
||||||
|
return err
|
||||||
|
}, progressOut, "Verifying internet access to caddy service")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, client.ErrNoReachableMachines) {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
||||||
|
"machines running caddy containers.\n", domain.Name)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Possible solutions:")
|
||||||
|
fmt.Println("- Ensure your machines have public IP addresses")
|
||||||
|
fmt.Println("- Use --public-ip flag when adding machines to override the automatically detected IPs")
|
||||||
|
fmt.Println("- Check firewall settings on your machines")
|
||||||
|
fmt.Println("- Configure port forwarding if behind NAT")
|
||||||
|
fmt.Println("- Retry Caddy deployment with 'uc caddy deploy' after resolving connectivity issues")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Your services won't be accessible from the internet until at least one machine " +
|
||||||
|
"becomes reachable. If you aren't planning to expose any services publicly, you can release " +
|
||||||
|
"the domain by running 'uc dns release'.")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to update DNS records pointing to caddy service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("DNS records updated to use only the internet-reachable machines running caddy service:")
|
||||||
|
for _, r := range records {
|
||||||
|
fmt.Printf(" %s %s → %s\n", r.Name, r.Type, strings.Join(r.Values, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
package caddy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolveDeploymentOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
customImage = "example.com/caddy:1.2.3"
|
||||||
|
customConfig = "{\n\tdebug\n}"
|
||||||
|
)
|
||||||
|
machines := api.Placement{Machines: []string{"m1", "m2"}}
|
||||||
|
|
||||||
|
// A service with a custom image, config, and machines that has the given labels.
|
||||||
|
service := func(labels ...string) api.ServiceSpec {
|
||||||
|
spec := api.ServiceSpec{
|
||||||
|
Name: client.CaddyServiceName,
|
||||||
|
Caddy: &api.CaddySpec{Config: customConfig},
|
||||||
|
Container: api.ContainerSpec{Image: customImage},
|
||||||
|
Placement: machines,
|
||||||
|
}
|
||||||
|
if len(labels) > 0 {
|
||||||
|
spec.Labels = make(map[string]string, len(labels))
|
||||||
|
for _, label := range labels {
|
||||||
|
spec.Labels[label] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
current api.ServiceSpec
|
||||||
|
opts deployOptions
|
||||||
|
config string
|
||||||
|
want client.CaddyDeploymentOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no service gets the defaults",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service that isn't managed gets the defaults",
|
||||||
|
current: service(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "managed service follows the default image and config and keeps machines",
|
||||||
|
current: service(api.LabelCaddyManaged),
|
||||||
|
want: client.CaddyDeploymentOptions{Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom image is kept and config follows the default",
|
||||||
|
current: service(api.LabelCaddyManaged, api.LabelCaddyImageCustom),
|
||||||
|
want: client.CaddyDeploymentOptions{Image: customImage, Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom config is kept and image follows the latest version",
|
||||||
|
current: service(api.LabelCaddyManaged, api.LabelCaddyConfigCustom),
|
||||||
|
want: client.CaddyDeploymentOptions{Config: new(customConfig), Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom image and config are kept",
|
||||||
|
current: service(api.LabelCaddyManaged, api.LabelCaddyImageCustom, api.LabelCaddyConfigCustom),
|
||||||
|
want: client.CaddyDeploymentOptions{Image: customImage, Config: new(customConfig), Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom empty config is kept",
|
||||||
|
current: api.ServiceSpec{
|
||||||
|
Name: client.CaddyServiceName,
|
||||||
|
Labels: map[string]string{api.LabelCaddyManaged: "", api.LabelCaddyConfigCustom: ""},
|
||||||
|
},
|
||||||
|
want: client.CaddyDeploymentOptions{Config: new("")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "image flag overrides the current image",
|
||||||
|
current: service(api.LabelCaddyManaged, api.LabelCaddyImageCustom),
|
||||||
|
opts: deployOptions{image: "caddy:2", imageChanged: true},
|
||||||
|
want: client.CaddyDeploymentOptions{Image: "caddy:2", Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty image flag resets to the latest version",
|
||||||
|
current: service(api.LabelCaddyManaged, api.LabelCaddyImageCustom),
|
||||||
|
opts: deployOptions{imageChanged: true},
|
||||||
|
want: client.CaddyDeploymentOptions{Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "caddyfile flag overrides the current config",
|
||||||
|
current: service(api.LabelCaddyManaged),
|
||||||
|
opts: deployOptions{caddyfile: "global.Caddyfile", caddyfileChanged: true},
|
||||||
|
config: "{\n\tstorage redis\n}",
|
||||||
|
want: client.CaddyDeploymentOptions{Config: new("{\n\tstorage redis\n}"), Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "caddyfile flag with an empty file sets a custom empty config",
|
||||||
|
current: service(api.LabelCaddyManaged),
|
||||||
|
opts: deployOptions{caddyfile: "empty.Caddyfile", caddyfileChanged: true},
|
||||||
|
want: client.CaddyDeploymentOptions{Config: new(""), Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty caddyfile flag resets to the default config",
|
||||||
|
current: service(api.LabelCaddyManaged, api.LabelCaddyConfigCustom),
|
||||||
|
opts: deployOptions{caddyfileChanged: true},
|
||||||
|
want: client.CaddyDeploymentOptions{Placement: machines},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "machine flag overrides the current machines",
|
||||||
|
current: service(api.LabelCaddyManaged),
|
||||||
|
opts: deployOptions{machines: []string{"m3,m4", "m5"}, machinesChanged: true},
|
||||||
|
want: client.CaddyDeploymentOptions{Placement: api.Placement{Machines: []string{"m3", "m4", "m5"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty machine flag resets to all machines",
|
||||||
|
current: service(api.LabelCaddyManaged),
|
||||||
|
opts: deployOptions{machines: []string{}, machinesChanged: true},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "flags apply to a service that isn't managed",
|
||||||
|
current: service(),
|
||||||
|
opts: deployOptions{
|
||||||
|
image: "caddy:2", imageChanged: true,
|
||||||
|
caddyfile: "global.Caddyfile", caddyfileChanged: true,
|
||||||
|
machines: []string{"m3"}, machinesChanged: true,
|
||||||
|
},
|
||||||
|
config: customConfig,
|
||||||
|
want: client.CaddyDeploymentOptions{
|
||||||
|
Image: "caddy:2",
|
||||||
|
Config: new(customConfig),
|
||||||
|
Placement: api.Placement{Machines: []string{"m3"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, resolveDeploymentOptions(tt.current, tt.opts, tt.config))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeploymentWarnings(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultImage = client.CaddyImage + ":0.1.3"
|
||||||
|
customImage = "example.com/caddy:1.2.3"
|
||||||
|
localConfig = "{\n\tdebug\n}"
|
||||||
|
override = "doesn't manage this caddy service yet"
|
||||||
|
storage = "certificate storage changes from"
|
||||||
|
keepStorage = "To keep the current storage"
|
||||||
|
module = "must include the Uncloud storage module"
|
||||||
|
)
|
||||||
|
managed := map[string]string{api.LabelCaddyManaged: ""}
|
||||||
|
|
||||||
|
service := func(image, config string, labels map[string]string) api.ServiceSpec {
|
||||||
|
spec := api.ServiceSpec{
|
||||||
|
Name: client.CaddyServiceName,
|
||||||
|
Container: api.ContainerSpec{Image: image},
|
||||||
|
Labels: labels,
|
||||||
|
}
|
||||||
|
if config != "" {
|
||||||
|
spec.Caddy = &api.CaddySpec{Config: config}
|
||||||
|
}
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
current api.ServiceSpec
|
||||||
|
target api.ServiceSpec
|
||||||
|
opts deployOptions
|
||||||
|
want []string
|
||||||
|
notWant []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "new service with defaults has no warnings",
|
||||||
|
target: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
notWant: []string{override, storage, module},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "new service with a custom image",
|
||||||
|
target: service(customImage, client.DefaultCaddyConfig, managed),
|
||||||
|
want: []string{module},
|
||||||
|
notWant: []string{override, storage},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service that isn't managed moves to cluster storage",
|
||||||
|
current: service("caddy:2.11.4", "", nil),
|
||||||
|
target: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
want: []string{override, "from local to cluster", keepStorage},
|
||||||
|
notWant: []string{module},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service that isn't managed already uses cluster storage",
|
||||||
|
current: service(defaultImage, client.DefaultCaddyConfig, nil),
|
||||||
|
target: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
want: []string{override},
|
||||||
|
notWant: []string{storage, module},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "managed service without changes has no warnings",
|
||||||
|
current: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
target: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
notWant: []string{override, storage, module},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "managed service upgrades the default image",
|
||||||
|
current: service(client.CaddyImage+":0.1.2", client.DefaultCaddyConfig, managed),
|
||||||
|
target: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
notWant: []string{override, storage, module},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom caddyfile moves to local storage",
|
||||||
|
current: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
target: service(defaultImage, localConfig, managed),
|
||||||
|
opts: deployOptions{caddyfile: "global.Caddyfile", caddyfileChanged: true},
|
||||||
|
want: []string{"from cluster to local"},
|
||||||
|
notWant: []string{override, keepStorage, module},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other storage module is named",
|
||||||
|
current: service(defaultImage, "{\n\tstorage redis\n}", managed),
|
||||||
|
target: service(defaultImage, client.DefaultCaddyConfig, managed),
|
||||||
|
want: []string{"from 'redis' to cluster", keepStorage},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom image is unchanged with the same config",
|
||||||
|
current: service(customImage, client.DefaultCaddyConfig, managed),
|
||||||
|
target: service(customImage, client.DefaultCaddyConfig, managed),
|
||||||
|
notWant: []string{override, storage, module},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom image receives a changed default config",
|
||||||
|
current: service(customImage, "{\n\tstorage uncloud\n\tdebug\n}", managed),
|
||||||
|
target: service(customImage, client.DefaultCaddyConfig, managed),
|
||||||
|
want: []string{module},
|
||||||
|
notWant: []string{override, storage},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom image with local storage",
|
||||||
|
current: service(defaultImage, localConfig, managed),
|
||||||
|
target: service(customImage, localConfig, managed),
|
||||||
|
notWant: []string{override, storage, module},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
warnings := strings.Join(deploymentWarnings(tt.current, tt.target, tt.opts), "\n")
|
||||||
|
for _, want := range tt.want {
|
||||||
|
assert.Contains(t, warnings, want)
|
||||||
|
}
|
||||||
|
for _, notWant := range tt.notWant {
|
||||||
|
assert.NotContains(t, warnings, notWant)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/psviderski/uncloud/cmd/uc/caddy/cert"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,8 +11,10 @@ func NewRootCommand() *cobra.Command {
|
|||||||
Short: "Manage Caddy reverse proxy service.",
|
Short: "Manage Caddy reverse proxy service.",
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
|
cert.NewRootCommand(),
|
||||||
NewConfigCommand(),
|
NewConfigCommand(),
|
||||||
NewDeployCommand(),
|
NewDeployCommand(),
|
||||||
|
NewLogsCommand(),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -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",
|
Use: "connection",
|
||||||
Aliases: []string{"conn"},
|
Aliases: []string{"conn"},
|
||||||
Short: "Choose a new default connection for the current context.",
|
Short: "Choose a new default connection for the current context.",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return selectConnection(uncli)
|
return selectConnection(uncli)
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -15,6 +16,7 @@ func NewListCommand() *cobra.Command {
|
|||||||
Use: "ls",
|
Use: "ls",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"list"},
|
||||||
Short: "List available cluster contexts.",
|
Short: "List available cluster contexts.",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return list(uncli)
|
return list(uncli)
|
||||||
@@ -49,6 +51,6 @@ func list(uncli *cli.CLI) error {
|
|||||||
t.Row(name, current, fmt.Sprintf("%d", connCount))
|
t.Row(name, current, fmt.Sprintf("%d", connCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
lipgloss.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ func NewRootCommand() *cobra.Command {
|
|||||||
Use: "ctx",
|
Use: "ctx",
|
||||||
Aliases: []string{"context"},
|
Aliases: []string{"context"},
|
||||||
Short: "Switch between different cluster contexts. Contains subcommands to manage contexts.",
|
Short: "Switch between different cluster contexts. Contains subcommands to manage contexts.",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return selectContext(uncli)
|
return selectContext(uncli)
|
||||||
@@ -11,6 +11,7 @@ func NewShowCommand() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "show",
|
Use: "show",
|
||||||
Short: "Show current cluster context.",
|
Short: "Show current cluster context.",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return show(uncli)
|
return show(uncli)
|
||||||
@@ -21,15 +22,6 @@ func NewShowCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func show(uncli *cli.CLI) error {
|
func show(uncli *cli.CLI) error {
|
||||||
// discard errors, only show the current context, otherwise nothing
|
fmt.Println(uncli.ContextOverrideOrCurrent())
|
||||||
if uncli.Config == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(uncli.Config.Contexts) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
fmt.Println(uncli.Config.CurrentContext)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"charm.land/huh/v2"
|
"charm.land/huh/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ func NewUseCommand() *cobra.Command {
|
|||||||
Short: "Switch to a different cluster context.",
|
Short: "Switch to a different cluster context.",
|
||||||
Long: "Switch to a different cluster context. If no context is provided, " +
|
Long: "Switch to a different cluster context. If no context is provided, " +
|
||||||
"a list of available contexts will be displayed for selection.",
|
"a list of available contexts will be displayed for selection.",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
@@ -49,6 +51,10 @@ func selectContext(uncli *cli.CLI) error {
|
|||||||
if len(uncli.Config.Contexts) == 0 {
|
if len(uncli.Config.Contexts) == 0 {
|
||||||
return fmt.Errorf("no contexts found in Uncloud config (%s)", uncli.Config.Path())
|
return fmt.Errorf("no contexts found in Uncloud config (%s)", uncli.Config.Path())
|
||||||
}
|
}
|
||||||
|
if !tui.IsTerminalAvailable() {
|
||||||
|
return fmt.Errorf("cannot select a context interactively without a terminal. " +
|
||||||
|
"Pass the context name explicitly: uc ctx use CONTEXT")
|
||||||
|
}
|
||||||
|
|
||||||
contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/psviderski/uncloud/pkg/client/compose"
|
"github.com/psviderski/uncloud/pkg/client/compose"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy/operation"
|
"github.com/psviderski/uncloud/pkg/client/operation"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,6 +163,14 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve 'secret://name' references to actual secret values before creating a deployment.
|
||||||
|
if compose.HasCommandSecretRefs(project) {
|
||||||
|
fmt.Fprintln(os.Stderr, "Resolving secrets...")
|
||||||
|
}
|
||||||
|
if err = compose.ResolveSecrets(ctx, project); err != nil {
|
||||||
|
return fmt.Errorf("resolve secrets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
strategy := &deploy.RollingStrategy{
|
strategy := &deploy.RollingStrategy{
|
||||||
ForceRecreate: opts.recreate,
|
ForceRecreate: opts.recreate,
|
||||||
SkipHealthMonitor: opts.skipHealth,
|
SkipHealthMonitor: opts.skipHealth,
|
||||||
@@ -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.
|
// Ask for plan confirmation before proceeding with the deployment unless auto-confirmed with --yes.
|
||||||
if !opts.yes {
|
if !opts.yes {
|
||||||
if !tui.IsStdinTerminal() {
|
if !tui.IsTerminalAvailable() {
|
||||||
return errors.New("cannot ask to confirm deployment plan in non-interactive mode, " +
|
return errors.New("cannot ask to confirm deployment plan in non-interactive mode, " +
|
||||||
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
||||||
}
|
}
|
||||||
@@ -297,7 +305,7 @@ const defaultFailedContainerLogsTail = 10
|
|||||||
// UNCLOUD_FAILED_CONTAINER_LOGS_TAIL environment variable override when set and valid.
|
// UNCLOUD_FAILED_CONTAINER_LOGS_TAIL environment variable override when set and valid.
|
||||||
func failedContainerLogsTail() int {
|
func failedContainerLogsTail() int {
|
||||||
if v := os.Getenv("UNCLOUD_FAILED_CONTAINER_LOGS_TAIL"); v != "" {
|
if v := os.Getenv("UNCLOUD_FAILED_CONTAINER_LOGS_TAIL"); v != "" {
|
||||||
if tail, err := logs.Tail(v); err == nil && (tail == -1 || tail > 0) {
|
if tail, err := logs.ParseTail(v); err == nil && (tail == -1 || tail > 0) {
|
||||||
return tail
|
return tail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,6 +37,7 @@ func release(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
if status.Convert(err).Code() == codes.NotFound {
|
if status.Convert(err).Code() == codes.NotFound {
|
||||||
return errors.New("no domain reserved")
|
return errors.New("no domain reserved")
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("release cluster domain: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Released cluster domain: %s\n", domain.Name)
|
fmt.Printf("Released cluster domain: %s\n", domain.Name)
|
||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/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/cli"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -49,7 +49,8 @@ func reserve(ctx context.Context, uncli *cli.CLI, opts reserveOptions) error {
|
|||||||
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.endpoint})
|
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.endpoint})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if status.Convert(err).Code() == codes.AlreadyExists {
|
if status.Convert(err).Code() == codes.AlreadyExists {
|
||||||
return errors.New("domain already reserved")
|
return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' " +
|
||||||
|
"or release a reservation with 'uc dns release' first")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -7,16 +7,19 @@ import (
|
|||||||
func NewRootCommand() *cobra.Command {
|
func NewRootCommand() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "dns",
|
Use: "dns",
|
||||||
Short: "Manage cluster domain in Uncloud DNS.",
|
Short: "Manage the cluster domain.",
|
||||||
Long: "Manage cluster domain in Uncloud DNS.\n" +
|
Long: "Manage the cluster domain.\n" +
|
||||||
"DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your " +
|
"DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your " +
|
||||||
"cluster. When reserved, Caddy service deployments will automatically update DNS records to route " +
|
"cluster. When reserved, Caddy service deployments will automatically update DNS records to route " +
|
||||||
"traffic to the services in the cluster.",
|
"traffic to the services in the cluster.\n\n" +
|
||||||
|
"EXPERIMENTAL: Use 'uc dns set' to configure an externally managed domain instead. " +
|
||||||
|
"Uncloud does not manage DNS records for manually set domains.",
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
NewReleaseCommand(),
|
NewReleaseCommand(),
|
||||||
NewReserveCommand(),
|
NewReserveCommand(),
|
||||||
NewShowCommand(),
|
NewShowCommand(),
|
||||||
|
NewSetCommand(),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/psviderski/uncloud/api/pb"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/internal/grpcversion"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSetCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "set DOMAIN_NAME",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Short: "Set or unset an externally managed cluster domain (EXPERIMENTAL).",
|
||||||
|
Long: "EXPERIMENTAL: Set the cluster domain used to generate ingress hostnames for services.\n" +
|
||||||
|
"Configure wildcard DNS records for this domain with your DNS provider. " +
|
||||||
|
"Uncloud will not create, verify, update, or delete external DNS records.\n" +
|
||||||
|
"Pass an empty string to unset a manually set domain. " +
|
||||||
|
"Use 'uc dns release' to release a domain reserved in Uncloud DNS. " +
|
||||||
|
"Setting or unsetting the domain does not change existing service hostnames.",
|
||||||
|
Example: " uc dns set apps.example.com\n uc dns set \"\"",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return set(cmd.Context(), uncli, args[0])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(ctx context.Context, uncli *cli.CLI, name string) error {
|
||||||
|
if name != "" {
|
||||||
|
if labels, ok := dns.IsDomainName(name); !ok || labels < 2 {
|
||||||
|
return fmt.Errorf("invalid cluster domain %q: must be a valid domain name with at least two labels", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tui.PrintWarning("Setting an externally managed cluster domain is experimental.")
|
||||||
|
|
||||||
|
clusterClient, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer clusterClient.Close()
|
||||||
|
|
||||||
|
ctx = metadata.AppendToOutgoingContext(ctx, grpcversion.MetadataKeyMinServerVersion, "0.21.0")
|
||||||
|
_, err = clusterClient.SetDomain(ctx, &pb.SetDomainRequest{Name: name})
|
||||||
|
if err != nil {
|
||||||
|
if status.Convert(err).Code() == codes.AlreadyExists {
|
||||||
|
return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' or " +
|
||||||
|
"release a reservation with 'uc dns release' first")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("set cluster domain: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
fmt.Println("Unset cluster domain. External DNS records have not been changed.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("Set cluster domain: %s\n", name)
|
||||||
|
fmt.Println("DNS is managed externally. " +
|
||||||
|
"Configure wildcard DNS records pointing to machines running the Caddy service.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -33,11 +33,11 @@ func show(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
domain, err := clusterClient.GetDomain(ctx)
|
domain, err := clusterClient.GetDomain(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, api.ErrNotFound) {
|
if errors.Is(err, api.ErrNotFound) {
|
||||||
return errors.New("no domain reserved")
|
return errors.New("no cluster domain configured")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(domain)
|
fmt.Println(domain.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/charmbracelet/colorprofile"
|
"github.com/charmbracelet/colorprofile"
|
||||||
"github.com/containerd/platforms"
|
"github.com/containerd/platforms"
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
@@ -88,24 +89,16 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
}
|
}
|
||||||
defer clusterClient.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
// Get all machines to create ID to name mapping.
|
|
||||||
allMachines, err := clusterClient.ListMachines(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("list machines: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
machineIDToName := make(map[string]string)
|
|
||||||
for _, machineMember := range allMachines {
|
|
||||||
if machineMember.Machine != nil && machineMember.Machine.Id != "" && machineMember.Machine.Name != "" {
|
|
||||||
machineIDToName[machineMember.Machine.Id] = machineMember.Machine.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
machines := cli.ExpandCommaSeparatedValues(opts.machines)
|
machines := cli.ExpandCommaSeparatedValues(opts.machines)
|
||||||
|
|
||||||
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{
|
var clusterImages []api.MachineImages
|
||||||
Machines: machines,
|
err = tui.RunSpinner(ctx, "Listing images...", func(ctx context.Context) error {
|
||||||
Name: opts.nameFilter,
|
var err error
|
||||||
|
clusterImages, err = clusterClient.ListImages(ctx, api.ImageFilter{
|
||||||
|
Machines: machines,
|
||||||
|
Name: opts.nameFilter,
|
||||||
|
})
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list images: %w", err)
|
return fmt.Errorf("list images: %w", err)
|
||||||
@@ -115,60 +108,21 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
var rows []imageRow
|
var rows []imageRow
|
||||||
|
|
||||||
for _, machineImages := range clusterImages {
|
for _, machineImages := range clusterImages {
|
||||||
// Get machine name for better readability.
|
if err := machineImages.Metadata.Err(); err != nil {
|
||||||
machineName := machineImages.Metadata.Machine
|
tui.PrintWarning(fmt.Sprintf("failed to list images on machine '%s': %s",
|
||||||
if m := allMachines.FindByNameOrID(machineName); m != nil {
|
machineImages.Metadata.MachineName, err))
|
||||||
machineName = m.Machine.Name
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get machine name for better readability.
|
||||||
|
machineName := machineImages.Metadata.MachineName
|
||||||
|
|
||||||
store := "docker"
|
store := "docker"
|
||||||
if machineImages.ContainerdStore {
|
if machineImages.ContainerdStore {
|
||||||
store = "containerd"
|
store = "containerd"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each image for this machine.
|
rows = append(rows, imageRows(machineImages.Images, machineName, store)...)
|
||||||
for _, img := range machineImages.Images {
|
|
||||||
// Show the first 12 chars without 'sha256:' as the image ID like Docker does.
|
|
||||||
id := strings.TrimPrefix(img.ID, "sha256:")[:12]
|
|
||||||
|
|
||||||
name := "<none>"
|
|
||||||
if len(img.RepoTags) > 0 && img.RepoTags[0] != "<none>:<none>" {
|
|
||||||
name = img.RepoTags[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
imgPlatforms, _ := imagePlatforms(img)
|
|
||||||
formattedPlatforms := formatPlatforms(imgPlatforms)
|
|
||||||
|
|
||||||
created := ""
|
|
||||||
createdAt := time.Unix(img.Created, 0)
|
|
||||||
if !createdAt.IsZero() {
|
|
||||||
created = units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
|
|
||||||
}
|
|
||||||
|
|
||||||
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
|
|
||||||
|
|
||||||
// Check if the image is in use by any containers. Only supported by Docker API >=1.51
|
|
||||||
inUse := "-"
|
|
||||||
if img.Containers != -1 { // -1 means the info is not available.
|
|
||||||
if img.Containers > 0 {
|
|
||||||
inUse = lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Render("●")
|
|
||||||
} else {
|
|
||||||
inUse = lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render("○")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rows = append(rows, imageRow{
|
|
||||||
id: id,
|
|
||||||
name: name,
|
|
||||||
platforms: formattedPlatforms,
|
|
||||||
createdHuman: created,
|
|
||||||
createdUnix: img.Created,
|
|
||||||
size: size,
|
|
||||||
inUse: inUse,
|
|
||||||
store: store,
|
|
||||||
machine: machineName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rows) == 0 {
|
if len(rows) == 0 {
|
||||||
@@ -189,18 +143,60 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Print the images in a table format.
|
// Print the images in a table format.
|
||||||
fmt.Println(formatImageTable(rows))
|
lipgloss.Println(formatImageTable(rows))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// imageRows keeps every stored name visible and retains digest-only and unnamed images.
|
||||||
|
func imageRows(images []image.Summary, machineName, store string) []imageRow {
|
||||||
|
var rows []imageRow
|
||||||
|
for _, img := range images {
|
||||||
|
// RepoTags includes stored digest names on containerd. RepoDigests can include derived names.
|
||||||
|
imageNames := img.RepoTags
|
||||||
|
if len(imageNames) == 0 {
|
||||||
|
imageNames = []string{"<untagged>"}
|
||||||
|
}
|
||||||
|
|
||||||
|
imgPlatforms, _ := imagePlatforms(img)
|
||||||
|
created := ""
|
||||||
|
if img.Created > 0 {
|
||||||
|
created = units.HumanDuration(time.Since(time.Unix(img.Created, 0))) + " ago"
|
||||||
|
}
|
||||||
|
// Check if the image is in use by any containers. Only supported by Docker API >=1.51
|
||||||
|
inUse := "-"
|
||||||
|
if img.Containers >= 0 { // -1 means the info is not available.
|
||||||
|
inUse = lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render("○")
|
||||||
|
if img.Containers > 0 {
|
||||||
|
inUse = lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Render("●")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range imageNames {
|
||||||
|
rows = append(rows, imageRow{
|
||||||
|
id: stringid.TruncateID(img.ID),
|
||||||
|
name: name,
|
||||||
|
platforms: formatPlatforms(imgPlatforms),
|
||||||
|
createdHuman: created,
|
||||||
|
createdUnix: img.Created,
|
||||||
|
size: units.HumanSizeWithPrecision(float64(img.Size), 3),
|
||||||
|
inUse: inUse,
|
||||||
|
store: store,
|
||||||
|
machine: machineName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
// imagePlatforms returns a list of platforms supported by the image and a boolean indicating if it's multi-platform.
|
// imagePlatforms returns a list of platforms supported by the image and a boolean indicating if it's multi-platform.
|
||||||
func imagePlatforms(img image.Summary) ([]string, bool) {
|
func imagePlatforms(img image.Summary) ([]string, bool) {
|
||||||
var formattedPlatforms []string
|
var formattedPlatforms []string
|
||||||
multiPlatform := false
|
multiPlatform := false
|
||||||
|
|
||||||
for _, m := range img.Manifests {
|
for _, m := range img.Manifests {
|
||||||
if m.Kind != image.ManifestKindImage || !m.Available {
|
if m.Kind != image.ManifestKindImage || !m.Available || m.ImageData == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type removeOptions struct {
|
||||||
|
machines []string
|
||||||
|
yes bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoveCommand() *cobra.Command {
|
||||||
|
opts := removeOptions{}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "rm IMAGE [IMAGE...]",
|
||||||
|
Aliases: []string{"remove", "delete"},
|
||||||
|
Short: "Remove images from machines in the cluster.",
|
||||||
|
Long: `Remove images from all or selected machines in the cluster.
|
||||||
|
|
||||||
|
An image name without a tag selects all its tags. Specify a tag, such as myapp:latest, to remove
|
||||||
|
only that tag.
|
||||||
|
|
||||||
|
Wildcard patterns are supported for image names and tags and should be quoted to prevent your
|
||||||
|
shell from expanding them. Removing by image ID attempts to remove each name for that image
|
||||||
|
independently.
|
||||||
|
|
||||||
|
Images used by running or stopped containers cannot be removed. Unused tags can be removed even
|
||||||
|
when the same image is used under another tag. Failed removals do not prevent other removals.`,
|
||||||
|
Example: ` # Remove a tag from all machines after confirming the plan.
|
||||||
|
uc image rm myapp:old
|
||||||
|
|
||||||
|
# Remove all tags for an image name from all machines.
|
||||||
|
uc image rm myapp
|
||||||
|
|
||||||
|
# Remove matching tags from two machines without confirmation.
|
||||||
|
uc image rm 'myapp:1.*' 'otherapp:*' -m machine1,machine2 --yes
|
||||||
|
|
||||||
|
# Remove all names for an image by ID from all machines.
|
||||||
|
uc image rm ee8fed0e9618`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return remove(cmd.Context(), uncli, args, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil,
|
||||||
|
"Machine names or IDs to remove images from. Can be specified multiple times or as a comma-separated list.\n"+
|
||||||
|
"If not specified, the matching images will be removed from all machines.")
|
||||||
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
|
"Auto-confirm the removal plan. Should be explicitly set when running non-interactively,\n"+
|
||||||
|
"e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(ctx context.Context, uncli *cli.CLI, images []string, opts removeOptions) error {
|
||||||
|
clusterClient, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer clusterClient.Close()
|
||||||
|
|
||||||
|
plan, err := planImageRemoval(ctx, clusterClient, images, cli.ExpandCommaSeparatedValues(opts.machines))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("plan image removal: %w", err)
|
||||||
|
}
|
||||||
|
directConn := uncli.DirectConnection()
|
||||||
|
contextName := uncli.ContextOverrideOrCurrent()
|
||||||
|
cluster := ""
|
||||||
|
if directConn != "" {
|
||||||
|
cluster = directConn
|
||||||
|
lipgloss.Println(tui.Faint.Render("connection: ") + tui.NameStyle.Render(directConn))
|
||||||
|
lipgloss.Println()
|
||||||
|
} else if contextName != "" && len(uncli.Config.Contexts) > 1 {
|
||||||
|
// Only show context if there's more than one to avoid unnecessary clutter.
|
||||||
|
cluster = contextName
|
||||||
|
lipgloss.Println(tui.Faint.Render("context: ") + tui.NameStyle.Render(contextName))
|
||||||
|
lipgloss.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
lipgloss.Println(plan.Format())
|
||||||
|
|
||||||
|
if !opts.yes {
|
||||||
|
if !tui.IsTerminalAvailable() {
|
||||||
|
return errors.New("cannot ask to confirm image removal in non-interactive mode, " +
|
||||||
|
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
||||||
|
}
|
||||||
|
|
||||||
|
title := "Proceed with image removal?"
|
||||||
|
if cluster != "" {
|
||||||
|
isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
|
||||||
|
confirmStyle := tui.ThemeConfirm().Theme(isDark).Focused.Title
|
||||||
|
title = "Proceed with image removal on " + tui.NameStyle.Render(cluster) + confirmStyle.Render("?")
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmed, err := tui.Confirm(title)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("confirm image removal: %w", err)
|
||||||
|
}
|
||||||
|
if !confirmed {
|
||||||
|
return cli.Cancelled("Image removal cancelled. No images were removed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
untagged := make(map[string]bool)
|
||||||
|
var errs []error
|
||||||
|
for i, group := range plan.GroupPlans {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
|
return group.Execute(ctx, clusterClient, untagged)
|
||||||
|
}, uncli.ProgressOut(), "Removing image "+group.Image)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
var imageIDRegex = regexp.MustCompile(`^(sha256:)?[a-f0-9]{4,64}$`)
|
||||||
|
|
||||||
|
// imageRemovalPlan contains all image removal operations grouped by the user-supplied image name, ID, or pattern.
|
||||||
|
type imageRemovalPlan struct {
|
||||||
|
GroupPlans []*removeImageGroupPlan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *imageRemovalPlan) IsEmpty() bool { return len(p.GroupPlans) == 0 }
|
||||||
|
|
||||||
|
func (p *imageRemovalPlan) Format() string {
|
||||||
|
var out strings.Builder
|
||||||
|
machines := make(map[string]struct{})
|
||||||
|
removeCount := 0
|
||||||
|
for _, group := range p.GroupPlans {
|
||||||
|
out.WriteString(group.Format())
|
||||||
|
for _, op := range group.Operations {
|
||||||
|
machines[op.MachineName] = struct{}{}
|
||||||
|
removeCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summary := fmt.Sprintf("%s remove %s across %s %s",
|
||||||
|
tui.BoldRed.Render(fmt.Sprint(removeCount)),
|
||||||
|
tui.Faint.Render("·"),
|
||||||
|
tui.Bold.Render(fmt.Sprint(len(machines))),
|
||||||
|
pluralMachines(len(machines)),
|
||||||
|
)
|
||||||
|
out.WriteString("\n" + tui.Faint.Render(strings.Repeat("─", lipgloss.Width(summary))) + "\n" + summary + "\n")
|
||||||
|
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluralMachines(count int) string {
|
||||||
|
if count == 1 {
|
||||||
|
return "machine"
|
||||||
|
}
|
||||||
|
return "machines"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute applies the frozen plan, collecting failures without discovering new targets.
|
||||||
|
func (p *imageRemovalPlan) Execute(ctx context.Context, cli api.ImageClient) error {
|
||||||
|
untagged := make(map[string]bool)
|
||||||
|
var errs []error
|
||||||
|
for _, group := range p.GroupPlans {
|
||||||
|
if err := group.Execute(ctx, cli, untagged); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeImageGroupPlan groups removals for one image name, ID, or pattern supplied by the user.
|
||||||
|
type removeImageGroupPlan struct {
|
||||||
|
// Image is the image name, ID, or pattern supplied by the user.
|
||||||
|
Image string
|
||||||
|
Operations []*removeImageOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *removeImageGroupPlan) Execute(ctx context.Context, cli api.ImageClient, untagged map[string]bool) error {
|
||||||
|
var errs []error
|
||||||
|
for _, op := range o.Operations {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return errors.Join(append(errs, err)...)
|
||||||
|
}
|
||||||
|
if err := op.Execute(ctx, cli, untagged); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *removeImageGroupPlan) Format() string {
|
||||||
|
var out strings.Builder
|
||||||
|
out.WriteString(tui.BoldRed.Render("-") + " remove image " + tui.NameStyle.Render(o.Image) + "\n")
|
||||||
|
for i, op := range o.Operations {
|
||||||
|
branch := " ├── "
|
||||||
|
if i == len(o.Operations)-1 {
|
||||||
|
branch = " ╰── "
|
||||||
|
}
|
||||||
|
out.WriteString(tui.Faint.Render(branch) + op.Format() + "\n")
|
||||||
|
}
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *removeImageGroupPlan) String() string {
|
||||||
|
return fmt.Sprintf("removeImageGroupPlan[image=%s]", o.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeImageOperation removes an image name or an unnamed image by ID on a machine.
|
||||||
|
type removeImageOperation struct {
|
||||||
|
MachineName string
|
||||||
|
ImageID string
|
||||||
|
// Image is the exact image name or ID sent to Docker.
|
||||||
|
Image string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *removeImageOperation) Execute(ctx context.Context, cli api.ImageClient, untagged map[string]bool) error {
|
||||||
|
if untagged[o.MachineName+"\x00"+o.Image] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
results, err := cli.RemoveImage(ctx, o.Image, api.RemoveImageOptions{Machines: []string{o.MachineName}})
|
||||||
|
// A removal may also untag other planned names. Track actual results across all groups, per machine.
|
||||||
|
for _, result := range results {
|
||||||
|
for _, removal := range result.Results {
|
||||||
|
if removal.Untagged != "" {
|
||||||
|
untagged[result.Metadata.MachineName+"\x00"+removal.Untagged] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errors.Is(err, api.ErrNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("remove image '%s': %w", o.Image, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *removeImageOperation) Format() string {
|
||||||
|
return tui.BoldRed.Render("-") + " " + tui.FormatImage(o.Image, tui.NoStyle) +
|
||||||
|
tui.Faint.Render(" ("+stringid.TruncateID(o.ImageID)+") on ") + o.MachineName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *removeImageOperation) String() string {
|
||||||
|
return fmt.Sprintf("removeImageOperation[machine_name=%s image=%s]", o.MachineName, o.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
type machineImageInventory struct {
|
||||||
|
machineName string
|
||||||
|
images []image.Summary
|
||||||
|
}
|
||||||
|
|
||||||
|
// planImageRemoval builds a removal plan grouped by input argument from images on the selected machines.
|
||||||
|
// An empty machines list selects all machines.
|
||||||
|
//
|
||||||
|
// Arguments expand as follows:
|
||||||
|
// - Name: without a tag or digest, select all stored names in that exact repository across image IDs.
|
||||||
|
// An explicit tag selects only that tag. Familiar names are normalised.
|
||||||
|
// - ID: select all RepoTags for the matching image, or its full ID if unnamed. A prefix must identify
|
||||||
|
// one unique full ID across the selected machines.
|
||||||
|
// - Pattern: match RepoTags only. A matching digest also selects that image's names in the same repository.
|
||||||
|
// - Digest: resolve through RepoTags, RepoDigests, or the image ID, then select that image's names in
|
||||||
|
// the same repository. If none exist but RepoDigests contains the reference, select the digest itself.
|
||||||
|
//
|
||||||
|
// Selecting all tags for a repository/image on a machine also adds its stored digest references after
|
||||||
|
// the tags, even if those digests did not match an argument. Stored names are kept as is, and overlapping
|
||||||
|
// selections produce only one operation per machine and name.
|
||||||
|
//
|
||||||
|
// Failed image listings produce warnings and exclude those machines from the plan. If no machine can
|
||||||
|
// be queried, planning fails. Every argument must match on a successfully queried machine. Unmatched
|
||||||
|
// or invalid arguments and ambiguous ID prefixes reject the whole plan before any removal.
|
||||||
|
//
|
||||||
|
// Planning does not check container usage. The backend checks each removal during execution, and failures
|
||||||
|
// do not stop other operations. Tag removal may implicitly untag a used digest. Execution skips names
|
||||||
|
// already untagged by earlier operations and tolerates names that disappeared after planning.
|
||||||
|
func planImageRemoval(ctx context.Context, cli api.ImageClient, images, machines []string) (*imageRemovalPlan, error) {
|
||||||
|
if len(images) == 0 {
|
||||||
|
return nil, errors.New("at least one image is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var clusterImages []api.MachineImages
|
||||||
|
err := tui.RunSpinner(ctx, "Finding images to remove...", func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
clusterImages, err = cli.ListImages(ctx, api.ImageFilter{Machines: machines})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list images: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var inventory []*machineImageInventory
|
||||||
|
for _, machineImages := range clusterImages {
|
||||||
|
if err := machineImages.Metadata.Err(); err != nil {
|
||||||
|
tui.PrintWarning(fmt.Sprintf("failed to list images on machine '%s': %s",
|
||||||
|
machineImages.Metadata.MachineName, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
machine := &machineImageInventory{
|
||||||
|
machineName: machineImages.Metadata.MachineName,
|
||||||
|
images: machineImages.Images,
|
||||||
|
}
|
||||||
|
inventory = append(inventory, machine)
|
||||||
|
}
|
||||||
|
if len(inventory) == 0 {
|
||||||
|
return nil, errors.New("failed to list images on any machine")
|
||||||
|
}
|
||||||
|
|
||||||
|
return planImageRemovalFromInventory(images, inventory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func planImageRemovalFromInventory(images []string, inventory []*machineImageInventory) (*imageRemovalPlan, error) {
|
||||||
|
plan := &imageRemovalPlan{}
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var errs []error
|
||||||
|
for _, imageArg := range images {
|
||||||
|
group := &removeImageGroupPlan{Image: imageArg}
|
||||||
|
isID := imageIDRegex.MatchString(imageArg)
|
||||||
|
isPattern := strings.ContainsAny(imageArg, "*?[")
|
||||||
|
var namedRef reference.Named
|
||||||
|
if isPattern {
|
||||||
|
if _, err := path.Match(imageArg, ""); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid image pattern '%s': %w", imageArg, err)
|
||||||
|
}
|
||||||
|
} else if !isID {
|
||||||
|
var err error
|
||||||
|
namedRef, err = reference.ParseNormalizedNamed(imageArg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid image '%s': %w", imageArg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isID {
|
||||||
|
matches := make(map[string]bool)
|
||||||
|
for _, machine := range inventory {
|
||||||
|
for _, img := range machine.images {
|
||||||
|
if imageIDMatches(img.ID, imageArg) {
|
||||||
|
matches[img.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(matches) > 1 {
|
||||||
|
var candidates []string
|
||||||
|
for id := range matches {
|
||||||
|
candidates = append(candidates, id)
|
||||||
|
}
|
||||||
|
slices.Sort(candidates)
|
||||||
|
errs = append(errs,
|
||||||
|
fmt.Errorf("image ID '%s' is ambiguous: %s", imageArg, strings.Join(candidates, ", ")))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := false
|
||||||
|
for _, machine := range inventory {
|
||||||
|
for _, img := range machine.images {
|
||||||
|
var imagesToRemove []string
|
||||||
|
switch {
|
||||||
|
case isID:
|
||||||
|
if !imageIDMatches(img.ID, imageArg) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matched = true
|
||||||
|
// Expand only stored names, matching uc image ls. RepoDigests remains available for digest lookup.
|
||||||
|
imagesToRemove = img.RepoTags
|
||||||
|
if len(imagesToRemove) == 0 {
|
||||||
|
imagesToRemove = []string{img.ID}
|
||||||
|
}
|
||||||
|
case isPattern:
|
||||||
|
for _, ref := range img.RepoTags {
|
||||||
|
if ok, _ := path.Match(imageArg, ref); ok {
|
||||||
|
named, _ := reference.ParseDockerRef(ref)
|
||||||
|
if _, digest := named.(reference.Canonical); digest {
|
||||||
|
imagesToRemove = append(imagesToRemove,
|
||||||
|
imageRepositoryReferences(img.RepoTags, named.Name())...)
|
||||||
|
} else {
|
||||||
|
imagesToRemove = append(imagesToRemove, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reference.IsNameOnly(namedRef):
|
||||||
|
imagesToRemove = imageRepositoryReferences(img.RepoTags, namedRef.Name())
|
||||||
|
default:
|
||||||
|
if canonical, digest := namedRef.(reference.Canonical); digest {
|
||||||
|
ref := reference.FamiliarString(namedRef)
|
||||||
|
if canonical.Digest().String() != img.ID && !slices.Contains(img.RepoTags, ref) &&
|
||||||
|
!slices.Contains(img.RepoDigests, ref) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
imagesToRemove = imageRepositoryReferences(img.RepoTags, namedRef.Name())
|
||||||
|
if len(imagesToRemove) == 0 && slices.Contains(img.RepoDigests, ref) {
|
||||||
|
// An explicitly requested digest can resolve even when no stored name is listed.
|
||||||
|
imagesToRemove = []string{ref}
|
||||||
|
}
|
||||||
|
} else if slices.Contains(img.RepoTags, reference.FamiliarString(namedRef)) {
|
||||||
|
imagesToRemove = []string{reference.FamiliarString(namedRef)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ref := range imagesToRemove {
|
||||||
|
matched = true
|
||||||
|
key := machine.machineName + "\x00" + ref
|
||||||
|
if !seen[key] {
|
||||||
|
seen[key] = true
|
||||||
|
group.Operations = append(group.Operations, &removeImageOperation{
|
||||||
|
MachineName: machine.machineName, ImageID: img.ID, Image: ref,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
errs = append(errs, fmt.Errorf("image '%s' not found on any machine", imageArg))
|
||||||
|
}
|
||||||
|
slices.SortFunc(group.Operations, compareImageRemovalOperations)
|
||||||
|
if len(group.Operations) > 0 {
|
||||||
|
plan.GroupPlans = append(plan.GroupPlans, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
planDigestRemovals(plan, inventory)
|
||||||
|
if err := errors.Join(errs...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageRepositoryReferences(refs []string, repository string) []string {
|
||||||
|
var matches []string
|
||||||
|
for _, ref := range refs {
|
||||||
|
if named, err := reference.ParseDockerRef(ref); err == nil && named.Name() == repository {
|
||||||
|
matches = append(matches, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
// planDigestRemovals includes stored digests with the group removing their last repository tag.
|
||||||
|
// RepoDigests also contains synthesised references, so only digest names stored in RepoTags get operations here.
|
||||||
|
func planDigestRemovals(plan *imageRemovalPlan, inventory []*machineImageInventory) {
|
||||||
|
type repository struct {
|
||||||
|
remainingTags int
|
||||||
|
digests []string
|
||||||
|
}
|
||||||
|
repositories := make(map[string]*repository)
|
||||||
|
for _, machine := range inventory {
|
||||||
|
for _, img := range machine.images {
|
||||||
|
names := slices.Clone(img.RepoTags)
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, ref := range slices.Compact(names) {
|
||||||
|
named, err := reference.ParseDockerRef(ref)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := machine.machineName + "\x00" + img.ID + "\x00" + named.Name()
|
||||||
|
repo := repositories[key]
|
||||||
|
if repo == nil {
|
||||||
|
repo = &repository{}
|
||||||
|
repositories[key] = repo
|
||||||
|
}
|
||||||
|
if _, tagged := named.(reference.Tagged); tagged {
|
||||||
|
repo.remainingTags++
|
||||||
|
} else if _, digest := named.(reference.Canonical); digest {
|
||||||
|
repo.digests = append(repo.digests, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emitted := make(map[string]bool)
|
||||||
|
var groups []*removeImageGroupPlan
|
||||||
|
for _, group := range plan.GroupPlans {
|
||||||
|
var operations []*removeImageOperation
|
||||||
|
for _, op := range group.Operations {
|
||||||
|
key := op.MachineName + "\x00" + op.Image
|
||||||
|
if emitted[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
emitted[key] = true
|
||||||
|
operations = append(operations, op)
|
||||||
|
named, err := reference.ParseDockerRef(op.Image)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, tagged := named.(reference.Tagged); !tagged {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
repo := repositories[op.MachineName+"\x00"+op.ImageID+"\x00"+named.Name()]
|
||||||
|
if repo == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
repo.remainingTags--
|
||||||
|
if repo.remainingTags != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, digest := range repo.digests {
|
||||||
|
key := op.MachineName + "\x00" + digest
|
||||||
|
if !emitted[key] {
|
||||||
|
emitted[key] = true
|
||||||
|
operations = append(operations, &removeImageOperation{
|
||||||
|
MachineName: op.MachineName, ImageID: op.ImageID, Image: digest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.Operations = operations
|
||||||
|
slices.SortFunc(group.Operations, compareImageRemovalOperations)
|
||||||
|
if len(group.Operations) > 0 {
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plan.GroupPlans = groups
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareImageRemovalOperations(a, b *removeImageOperation) int {
|
||||||
|
aDigest, bDigest := strings.Contains(a.Image, "@"), strings.Contains(b.Image, "@")
|
||||||
|
if aDigest != bDigest {
|
||||||
|
if aDigest {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if c := strings.Compare(a.Image, b.Image); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return strings.Compare(a.MachineName, b.MachineName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageIDMatches(id, prefix string) bool {
|
||||||
|
return strings.HasPrefix(strings.TrimPrefix(id, "sha256:"), strings.TrimPrefix(prefix, "sha256:"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPlanImageRemovalFromInventory_Selectors(t *testing.T) {
|
||||||
|
idA, idB := testImageID("a"), testImageID("b")
|
||||||
|
digestRef := "myapp@" + testImageID("c")
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
selector string
|
||||||
|
inventory []*machineImageInventory
|
||||||
|
want []removeImageOperation
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "repository selects all stored names across images and machines", selector: "myapp",
|
||||||
|
inventory: []*machineImageInventory{
|
||||||
|
testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", "other:keep"),
|
||||||
|
testImage(idB, "myapp:latest"),
|
||||||
|
testImage(testImageID("c"), digestRef),
|
||||||
|
testImage(testImageID("d"), "myapp-extra:latest", "team/myapp:latest", "myapp/worker:latest"),
|
||||||
|
}),
|
||||||
|
testMachineInventory("machine2", []image.Summary{testImage(idB, "myapp:test")}),
|
||||||
|
},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idB, Image: "myapp:latest"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "machine2", ImageID: idB, Image: "myapp:test"},
|
||||||
|
{MachineName: "dev1", ImageID: testImageID("c"), Image: digestRef},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repository without latest includes its tags and stored digest", selector: "myapp",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", digestRef, "other:keep"),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: digestRef},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit latest selects only that tag", selector: "myapp:latest",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", "myapp:latest", digestRef),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "myapp:latest"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "qualified repository selects familiar names", selector: "docker.io/library/myapp",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", "myapp:latest", "other:keep"),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:latest"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry port does not count as an explicit tag", selector: "registry.example.com:5000/team/myapp",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "registry.example.com:5000/team/myapp:old", "registry.example.com:5000/team/myapp:new",
|
||||||
|
"registry.example.com:5001/team/myapp:keep", "registry.example.com:5000/team/myapp/worker:keep"),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "registry.example.com:5000/team/myapp:new"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "registry.example.com:5000/team/myapp:old"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit tag with registry port stays exact", selector: "registry.example.com:5000/team/myapp:old",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "registry.example.com:5000/team/myapp:old", "registry.example.com:5000/team/myapp:new"),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "registry.example.com:5000/team/myapp:old"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag uses each machines local image and allows missing copies", selector: "myapp:old",
|
||||||
|
inventory: []*machineImageInventory{
|
||||||
|
testMachineInventory("machine2", []image.Summary{testImage(idB, "myapp:old")}),
|
||||||
|
testMachineInventory("empty", nil),
|
||||||
|
testMachineInventory("dev1", []image.Summary{testImage(idA, "myapp:old")}),
|
||||||
|
},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "machine2", ImageID: idB, Image: "myapp:old"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "qualified names use familiar references", selector: "docker.io/library/myapp:old",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old"),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "myapp:old"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "full ID expands all repositories and deduplicates references per machine", selector: idA,
|
||||||
|
inventory: []*machineImageInventory{
|
||||||
|
testMachineInventory("dev1", []image.Summary{
|
||||||
|
{
|
||||||
|
ID: idA,
|
||||||
|
RepoTags: []string{"other:keep", "myapp:old", "myapp:old", digestRef, "other@" + testImageID("c")},
|
||||||
|
RepoDigests: []string{
|
||||||
|
digestRef, "other@" + testImageID("c"), "other@" + testImageID("c"), testImageID("c"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
testImage(idB, "unrelated:keep"),
|
||||||
|
}),
|
||||||
|
testMachineInventory("machine2", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{"myapp:old", digestRef}, RepoDigests: []string{digestRef},
|
||||||
|
}}),
|
||||||
|
},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "machine2", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "other:keep"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: digestRef},
|
||||||
|
{MachineName: "machine2", ImageID: idA, Image: digestRef},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "other@" + testImageID("c")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same full ID on multiple machines is unambiguous", selector: "aaaaaaaa",
|
||||||
|
inventory: []*machineImageInventory{
|
||||||
|
testMachineInventory("dev1", []image.Summary{testImage(idA, "myapp:old")}),
|
||||||
|
testMachineInventory("dev1", []image.Summary{testImage(idB, "myapp:latest")}),
|
||||||
|
testMachineInventory("machine2", []image.Summary{testImage(idA, "other:test")}),
|
||||||
|
},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "machine2", ImageID: idA, Image: "other:test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "short ID with algorithm prefix", selector: "sha256:aaaaaaaa",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1",
|
||||||
|
[]image.Summary{testImage(idA, "myapp:old")})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "myapp:old"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unnamed image is removed by its full ID", selector: idA,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{ID: idA}})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: idA}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ID expands only stored names", selector: idA,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{"myapp:latest"},
|
||||||
|
RepoDigests: []string{"myapp@" + idA, "other@" + idA},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "myapp:latest"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ID removes an untagged image by ID even if RepoDigests is populated", selector: idA,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoDigests: []string{digestRef},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: idA}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "synthesized digest selects repository tags without a digest row", selector: digestRef,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{"myapp:old", "other:keep", "myapp:latest"},
|
||||||
|
RepoDigests: []string{digestRef, "other@" + testImageID("c")},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:latest"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit digest lookup works without stored names", selector: digestRef,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoDigests: []string{digestRef},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: digestRef}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "synthesized digest resolves by target and repository even if not listed", selector: "myapp@" + idA,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{"myapp:old", "other:keep"}, RepoDigests: []string{"other@" + idA},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "myapp:old"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stored digest follows its repository tags", selector: digestRef,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", "myapp:latest", digestRef, "other:keep"),
|
||||||
|
testImage(idB, "myapp@"+idB),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:latest"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: digestRef},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "digest pattern also expands matching repository tags", selector: "myapp@sha256:c*",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", "myapp:latest", digestRef, "other:keep"),
|
||||||
|
testImage(idB, "myapp@"+idB),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:latest"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: digestRef},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "digest pattern matches stored names and ignores derived names", selector: "myapp@sha256:*",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", digestRef),
|
||||||
|
{ID: idB, RepoTags: []string{"myapp:latest"}, RepoDigests: []string{"myapp@" + idB}},
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: digestRef},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "last repository tag includes its stored digest", selector: "myapp:old",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", digestRef, "other:keep"),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:old"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: digestRef},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "last tag does not add a synthesized digest row", selector: "myapp:old",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{"myapp:old", "other:keep"}, RepoDigests: []string{digestRef},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "myapp:old"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remaining repository tag preserves its stored digest", selector: "myapp:old",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, "myapp:old", "myapp:latest", digestRef),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: "myapp:old"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "digest without repository tags remains executable", selector: digestRef,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(idA, digestRef, "other:keep"),
|
||||||
|
})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: digestRef}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "digest-only image keeps its named reference", selector: idA,
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{digestRef}, RepoDigests: []string{digestRef, testImageID("c")},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{{MachineName: "dev1", ImageID: idA, Image: digestRef}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag pattern selects only matching tags", selector: "myapp:1.[12]",
|
||||||
|
inventory: []*machineImageInventory{testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{"myapp:1.3", "myapp:1.2", "myapp:1.1"}, RepoDigests: []string{digestRef},
|
||||||
|
}})},
|
||||||
|
want: []removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:1.1"},
|
||||||
|
{MachineName: "dev1", ImageID: idA, Image: "myapp:1.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
plan, err := planImageRemovalFromInventory([]string{tt.selector}, tt.inventory)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, plan.GroupPlans, 1)
|
||||||
|
assert.Equal(t, tt.selector, plan.GroupPlans[0].Image)
|
||||||
|
|
||||||
|
var got []removeImageOperation
|
||||||
|
for _, op := range plan.GroupPlans[0].Operations {
|
||||||
|
got = append(got, *op)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanImageRemovalFromInventory_OverlappingSelectors(t *testing.T) {
|
||||||
|
inventory := []*machineImageInventory{
|
||||||
|
testMachineInventory("dev1", []image.Summary{testImage(testImageID("a"), "myapp:old", "myapp:latest")}),
|
||||||
|
testMachineInventory("machine2", []image.Summary{testImage(testImageID("b"), "myapp:test", "myapp:old")}),
|
||||||
|
}
|
||||||
|
|
||||||
|
plan, err := planImageRemovalFromInventory([]string{"myapp:old", "myapp", "myapp:*", testImageID("a"), "myapp:old"},
|
||||||
|
inventory)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, &imageRemovalPlan{GroupPlans: []*removeImageGroupPlan{
|
||||||
|
{Image: "myapp:old", Operations: []*removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: testImageID("a"), Image: "myapp:old"},
|
||||||
|
{MachineName: "machine2", ImageID: testImageID("b"), Image: "myapp:old"},
|
||||||
|
}},
|
||||||
|
{Image: "myapp", Operations: []*removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: testImageID("a"), Image: "myapp:latest"},
|
||||||
|
{MachineName: "machine2", ImageID: testImageID("b"), Image: "myapp:test"},
|
||||||
|
}},
|
||||||
|
}}, plan, "the first selector owns each removal and empty groups are omitted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanImageRemovalFromInventory_DigestFollowsLastTagAcrossSelectors(t *testing.T) {
|
||||||
|
imageID := testImageID("a")
|
||||||
|
digestRef := "myapp@" + imageID
|
||||||
|
inventory := []*machineImageInventory{testMachineInventory("dev1", []image.Summary{
|
||||||
|
testImage(imageID, "myapp:old", "myapp:latest", "myapp:old", digestRef, digestRef, "other:keep"),
|
||||||
|
})}
|
||||||
|
|
||||||
|
plan, err := planImageRemovalFromInventory([]string{"myapp:old", "myapp:latest", digestRef}, inventory)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, &imageRemovalPlan{GroupPlans: []*removeImageGroupPlan{
|
||||||
|
{Image: "myapp:old", Operations: []*removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: imageID, Image: "myapp:old"},
|
||||||
|
}},
|
||||||
|
{Image: "myapp:latest", Operations: []*removeImageOperation{
|
||||||
|
{MachineName: "dev1", ImageID: imageID, Image: "myapp:latest"},
|
||||||
|
{MachineName: "dev1", ImageID: imageID, Image: digestRef},
|
||||||
|
}},
|
||||||
|
}}, plan, "digest cleanup belongs to the last tag removal, so the later digest selector has no extra operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanImageRemovalFromInventory_RejectsInvalidOrMissingSelectors(t *testing.T) {
|
||||||
|
idA := "sha256:abcdef" + strings.Repeat("a", 58)
|
||||||
|
idB := "sha256:abcdef" + strings.Repeat("b", 58)
|
||||||
|
inventory := []*machineImageInventory{
|
||||||
|
testMachineInventory("dev1", []image.Summary{{
|
||||||
|
ID: idA, RepoTags: []string{"myapp:old"}, RepoDigests: []string{"myapp@" + idA, "derived@" + idA},
|
||||||
|
}}),
|
||||||
|
testMachineInventory("machine2", []image.Summary{{ID: idB, RepoTags: []string{"myapp:old"}}}),
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
images []string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{"missing repository", []string{"missing"}, "not found"},
|
||||||
|
{"repository only has a derived digest", []string{"derived"}, "not found"},
|
||||||
|
{"missing tag", []string{"myapp:missing"}, "not found"},
|
||||||
|
{"missing pattern", []string{"other:*"}, "not found"},
|
||||||
|
{"pattern only matches a derived digest", []string{"myapp@sha256:*"}, "not found"},
|
||||||
|
{"missing ID", []string{testImageID("c")}, "not found"},
|
||||||
|
{"ambiguous ID prefix", []string{"abcdef"}, "ambiguous"},
|
||||||
|
{"invalid name", []string{"MyApp:old"}, "invalid image"},
|
||||||
|
{"invalid pattern", []string{"myapp:["}, "invalid image pattern"},
|
||||||
|
{"one missing selector rejects the whole plan", []string{"myapp:old", "myapp:missing"}, "not found"},
|
||||||
|
{"one invalid selector rejects the whole plan", []string{"myapp:old", "myapp:["}, "invalid image pattern"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
plan, err := planImageRemovalFromInventory(tt.images, inventory)
|
||||||
|
require.ErrorContains(t, err, tt.err)
|
||||||
|
assert.Nil(t, plan, "a failed selector must not return a partial plan")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("ID prefix resolves within the supplied machines", func(t *testing.T) {
|
||||||
|
plan, err := planImageRemovalFromInventory([]string{"abcdef"}, inventory[:1])
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, plan.GroupPlans, 1)
|
||||||
|
require.Len(t, plan.GroupPlans[0].Operations, 1)
|
||||||
|
assert.Equal(t, idA, plan.GroupPlans[0].Operations[0].ImageID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanImageRemovalFromInventory_InUseImagesAreStillPlanned(t *testing.T) {
|
||||||
|
img := testImage(testImageID("a"), "myapp:old", "myapp:current")
|
||||||
|
img.Containers = 1
|
||||||
|
|
||||||
|
for _, selector := range []string{"myapp", "myapp:current", img.ID, "myapp:*"} {
|
||||||
|
t.Run(selector, func(t *testing.T) {
|
||||||
|
plan, err := planImageRemovalFromInventory([]string{selector}, []*machineImageInventory{
|
||||||
|
testMachineInventory("dev1", []image.Summary{img}),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, plan.IsEmpty(), "usage protection belongs to the backend at execution time")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testImageID(char string) string { return "sha256:" + strings.Repeat(char, 64) }
|
||||||
|
|
||||||
|
func testImage(imageID string, tags ...string) image.Summary {
|
||||||
|
return image.Summary{ID: imageID, RepoTags: tags}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMachineInventory(name string, images []image.Summary) *machineImageInventory {
|
||||||
|
return &machineImageInventory{machineName: name, images: images}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ func NewRootCommand() *cobra.Command {
|
|||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
NewListCommand(),
|
NewListCommand(),
|
||||||
NewPushCommand(),
|
NewPushCommand(),
|
||||||
|
NewRemoveCommand(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
@@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/image"
|
"github.com/psviderski/uncloud/cmd/uc/image"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -8,10 +8,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/huh/v2/spinner"
|
|
||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
@@ -29,6 +28,8 @@ type addOptions struct {
|
|||||||
sshKey string
|
sshKey string
|
||||||
version string
|
version string
|
||||||
wgEndpoints []string
|
wgEndpoints []string
|
||||||
|
wgPort int
|
||||||
|
wgMTU int
|
||||||
yes bool
|
yes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,13 +40,19 @@ func NewAddCommand() *cobra.Command {
|
|||||||
Short: "Add a remote machine to a cluster.",
|
Short: "Add a remote machine to a cluster.",
|
||||||
Long: `Add a new machine to an existing Uncloud cluster.
|
Long: `Add a new machine to an existing Uncloud cluster.
|
||||||
|
|
||||||
|
By default, it installs Docker and the Uncloud daemon on the machine over SSH unless --no-install
|
||||||
|
is specified, which assumes they are already installed and running.
|
||||||
|
|
||||||
Connection methods:
|
Connection methods:
|
||||||
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||||
ssh+go://user@host - Use Go's built-in SSH library`,
|
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
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, "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)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
// Determine connection mode and strip scheme.
|
// Determine connection mode and strip scheme.
|
||||||
@@ -70,14 +77,15 @@ Connection methods:
|
|||||||
return add(cmd.Context(), uncli, remoteMachine, opts)
|
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(
|
cmd.Flags().BoolVar(
|
||||||
&opts.noCaddy, "no-caddy", false,
|
&opts.noCaddy, "no-caddy", false,
|
||||||
"Don't deploy Caddy reverse proxy service to the machine.",
|
"Don't deploy Caddy reverse proxy service to the machine.",
|
||||||
)
|
)
|
||||||
cmd.Flags().BoolVar(
|
cmd.Flags().BoolVar(
|
||||||
&opts.noInstall, "no-install", false,
|
&opts.noInstall, "no-install", false,
|
||||||
"Skip installation of Docker, 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.",
|
"Assumes they're already installed and running.",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
@@ -92,17 +100,25 @@ Connection methods:
|
|||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.version, "version", "latest",
|
&opts.version, "version", "latest",
|
||||||
"Version of the Uncloud daemon to install on the machine.",
|
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&opts.wgEndpoints, "wg-endpoint", nil,
|
&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"+
|
"WireGuard connections\n"+
|
||||||
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n", 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"+
|
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.\n"+
|
||||||
"Defaults to the auto-detected public and routable machine IPs.",
|
"Defaults to the auto-detected public and routable machine IPs.",
|
||||||
)
|
)
|
||||||
|
cmd.Flags().IntVar(
|
||||||
|
&opts.wgMTU, "wg-mtu", 0,
|
||||||
|
"MTU of the WireGuard network interface on the machine. (default auto-detects the optimal value)",
|
||||||
|
)
|
||||||
|
cmd.Flags().IntVar(
|
||||||
|
&opts.wgPort, "wg-port", network.DefaultWireGuardPort,
|
||||||
|
"UDP port WireGuard listens on for incoming connections from other machines.",
|
||||||
|
)
|
||||||
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
||||||
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
||||||
@@ -125,17 +141,26 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.wgPort < 1 || opts.wgPort > 65535 {
|
||||||
|
return fmt.Errorf("invalid WireGuard port %d: must be between 1 and 65535", opts.wgPort)
|
||||||
|
}
|
||||||
|
if opts.wgMTU != 0 && (opts.wgMTU < network.MinWireGuardMTU || opts.wgMTU > 65535) {
|
||||||
|
return fmt.Errorf("invalid WireGuard MTU %d: must be 0 (auto-detect) or between %d and 65535",
|
||||||
|
opts.wgMTU, network.MinWireGuardMTU)
|
||||||
|
}
|
||||||
addOpts := cli.AddMachineOptions{
|
addOpts := cli.AddMachineOptions{
|
||||||
MachineName: opts.name,
|
MachineName: opts.name,
|
||||||
PublicIP: publicIP,
|
PublicIP: publicIP,
|
||||||
RemoteMachine: remoteMachine,
|
RemoteMachine: remoteMachine,
|
||||||
SkipInstall: opts.noInstall,
|
SkipInstall: opts.noInstall,
|
||||||
Version: opts.version,
|
Version: opts.version,
|
||||||
|
WireguardMTU: opts.wgMTU,
|
||||||
|
WireguardPort: opts.wgPort,
|
||||||
AutoConfirm: opts.yes,
|
AutoConfirm: opts.yes,
|
||||||
}
|
}
|
||||||
if len(opts.wgEndpoints) > 0 {
|
if len(opts.wgEndpoints) > 0 {
|
||||||
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
||||||
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
|
endpoints, err := cli.ParseWireGuardEndpoints(expanded, uint16(opts.wgPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
||||||
}
|
}
|
||||||
@@ -154,32 +179,20 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the cluster to be initialised on the machine to be able to deploy the Caddy service.
|
// Wait for the cluster to be initialised on the machine to be able to deploy the Caddy service.
|
||||||
err = spinner.New().
|
err = tui.RunSpinner(ctx, "Waiting for the machine to join the cluster...", func(ctx context.Context) error {
|
||||||
Title(" Waiting for the machine to join the cluster...").
|
return machineClient.WaitClusterReady(ctx, 5*time.Minute)
|
||||||
Type(spinner.MiniDot).
|
})
|
||||||
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
|
|
||||||
return &spinner.Styles{
|
|
||||||
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
|
|
||||||
Title: lipgloss.NewStyle(),
|
|
||||||
}
|
|
||||||
})).
|
|
||||||
ActionWithErr(func(ctx context.Context) error {
|
|
||||||
return machineClient.WaitClusterReady(ctx, 5*time.Minute)
|
|
||||||
}).
|
|
||||||
Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wait for machine to join the cluster: %w", err)
|
return fmt.Errorf("wait for machine to join the cluster: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Machine joined the cluster.")
|
fmt.Println("Machine joined the cluster.")
|
||||||
|
|
||||||
// TODO: scale the existing Caddy service to the new machine instead of running a new deployment
|
// Deploy the current caddy service spec to run a container on the added machine. This keeps the service
|
||||||
// that may cause a small downtime.
|
// configuration however it was deployed and shouldn't change the containers on other machines if specs on them
|
||||||
// Deploy a Caddy service container to the added machine. If caddy service is already deployed on other machines,
|
// haven't diverged.
|
||||||
// use the deployed image version.
|
|
||||||
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
|
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
|
||||||
// issues accessing the Machine API of existing machines in the cluster.
|
// issues accessing the API of existing machines in the cluster.
|
||||||
// See the issue for more details: https://github.com/psviderski/uncloud/issues/65.
|
// See the issue for more details: https://github.com/psviderski/uncloud/issues/65.
|
||||||
caddyImage := ""
|
|
||||||
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, api.ErrNotFound) {
|
if errors.Is(err, api.ErrNotFound) {
|
||||||
@@ -188,26 +201,10 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("inspect caddy service: %w", err)
|
return fmt.Errorf("inspect caddy service: %w", err)
|
||||||
}
|
}
|
||||||
caddyImage = caddySvc.Containers[0].Container.Config.Image
|
|
||||||
// Find the latest created container and use its image.
|
|
||||||
var latestCreated time.Time
|
|
||||||
for _, c := range caddySvc.Containers[1:] {
|
|
||||||
created, err := time.Parse(time.RFC3339Nano, c.Container.Created)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if created.After(latestCreated) {
|
|
||||||
latestCreated = created
|
|
||||||
caddyImage = c.Container.Config.Image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Preparing Caddy deployment...")
|
fmt.Println("Preparing Caddy deployment...")
|
||||||
d, err := clusterClient.NewCaddyDeployment(caddyImage, "", api.Placement{})
|
d := clusterClient.NewDeployment(caddySvc.Spec(), nil)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create caddy deployment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
plan, err := d.Plan(ctx)
|
plan, err := d.Plan(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -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,17 +7,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/huh/v2/spinner"
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
"github.com/psviderski/uncloud/api/pb"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/dns"
|
"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"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"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/cluster"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +32,8 @@ type initOptions struct {
|
|||||||
sshKey string
|
sshKey string
|
||||||
version string
|
version string
|
||||||
wgEndpoints []string
|
wgEndpoints []string
|
||||||
|
wgPort int
|
||||||
|
wgMTU int
|
||||||
yes bool
|
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.
|
Long: `Initialise a new cluster by setting up a remote machine as the first member.
|
||||||
This command creates a new context in your Uncloud config to manage the cluster.
|
This command creates a new context in your Uncloud config to manage the cluster.
|
||||||
|
|
||||||
|
By default, it installs Docker and the Uncloud daemon on the machine over SSH unless --no-install
|
||||||
|
is specified, which assumes they are already installed and running.
|
||||||
|
|
||||||
Connection methods:
|
Connection methods:
|
||||||
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||||
ssh+go://user@host - Use Go's built-in SSH library`,
|
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||||
@@ -61,9 +65,12 @@ Connection methods:
|
|||||||
uc machine init root@<your-server-ip> --no-caddy --no-dns`,
|
uc machine init root@<your-server-ip> --no-caddy --no-dns`,
|
||||||
// TODO: support initialising a cluster on the local machine.
|
// TODO: support initialising a cluster on the local machine.
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
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, "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)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
var remoteMachine *cli.RemoteMachine
|
var remoteMachine *cli.RemoteMachine
|
||||||
@@ -100,7 +107,7 @@ Connection methods:
|
|||||||
"API endpoint for the Uncloud DNS service.")
|
"API endpoint for the Uncloud DNS service.")
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.name, "name", "n", "",
|
&opts.name, "name", "n", "",
|
||||||
"Assign a name to the machine.",
|
"Assign a name to the machine. (default is the machine's hostname)",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.network, "network", cluster.DefaultNetwork.String(),
|
&opts.network, "network", cluster.DefaultNetwork.String(),
|
||||||
@@ -116,7 +123,7 @@ Connection methods:
|
|||||||
)
|
)
|
||||||
cmd.Flags().BoolVar(
|
cmd.Flags().BoolVar(
|
||||||
&opts.noInstall, "no-install", false,
|
&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.",
|
"Assumes they're already installed and running.",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
@@ -131,17 +138,25 @@ Connection methods:
|
|||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.version, "version", "latest",
|
&opts.version, "version", "latest",
|
||||||
"Version of the Uncloud daemon to install on the machine.",
|
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&opts.wgEndpoints, "wg-endpoint", nil,
|
&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"+
|
"WireGuard connections\n"+
|
||||||
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n", 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"+
|
"Multiple endpoints can be specified by repeating the flag or using a comma-separated list.\n"+
|
||||||
"Defaults to the auto-detected public and routable machine IPs.",
|
"Defaults to the auto-detected public and routable machine IPs.",
|
||||||
)
|
)
|
||||||
|
cmd.Flags().IntVar(
|
||||||
|
&opts.wgMTU, "wg-mtu", 0,
|
||||||
|
"MTU of the WireGuard network interface on the machine. (default auto-detects the optimal value)",
|
||||||
|
)
|
||||||
|
cmd.Flags().IntVar(
|
||||||
|
&opts.wgPort, "wg-port", network.DefaultWireGuardPort,
|
||||||
|
"UDP port WireGuard listens on for incoming connections from other machines.",
|
||||||
|
)
|
||||||
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
"Auto-confirm prompts (e.g., resetting an already initialised machine).\n"+
|
||||||
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
"Should be explicitly set when running non-interactively, e.g., in CI/CD pipelines. [$UNCLOUD_AUTO_CONFIRM]")
|
||||||
@@ -178,6 +193,13 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
}
|
}
|
||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
|
if opts.wgPort < 1 || opts.wgPort > 65535 {
|
||||||
|
return fmt.Errorf("invalid WireGuard port %d: must be between 1 and 65535", opts.wgPort)
|
||||||
|
}
|
||||||
|
if opts.wgMTU != 0 && (opts.wgMTU < network.MinWireGuardMTU || opts.wgMTU > 65535) {
|
||||||
|
return fmt.Errorf("invalid WireGuard MTU %d: must be 0 (auto-detect) or between %d and 65535",
|
||||||
|
opts.wgMTU, network.MinWireGuardMTU)
|
||||||
|
}
|
||||||
initOpts := cli.InitClusterOptions{
|
initOpts := cli.InitClusterOptions{
|
||||||
Context: opts.context,
|
Context: opts.context,
|
||||||
MachineName: opts.name,
|
MachineName: opts.name,
|
||||||
@@ -186,39 +208,31 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
RemoteMachine: remoteMachine,
|
RemoteMachine: remoteMachine,
|
||||||
SkipInstall: opts.noInstall,
|
SkipInstall: opts.noInstall,
|
||||||
Version: opts.version,
|
Version: opts.version,
|
||||||
|
WireguardMTU: opts.wgMTU,
|
||||||
|
WireguardPort: opts.wgPort,
|
||||||
AutoConfirm: opts.yes,
|
AutoConfirm: opts.yes,
|
||||||
}
|
}
|
||||||
if len(opts.wgEndpoints) > 0 {
|
if len(opts.wgEndpoints) > 0 {
|
||||||
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
||||||
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
|
endpoints, err := cli.ParseWireGuardEndpoints(expanded, uint16(opts.wgPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
return fmt.Errorf("parse WireGuard endpoint (--wg-endpoint): %w", err)
|
||||||
}
|
}
|
||||||
initOpts.WireguardEndpoints = endpoints
|
initOpts.WireguardEndpoints = endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := uncli.InitCluster(ctx, initOpts)
|
clusterClient, err := uncli.InitCluster(ctx, initOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
// Since the cluster API needs a few moments to become ready after cluster initialisation,
|
// Since the cluster API needs a few moments to become ready after cluster initialisation,
|
||||||
// we keep the user informed during this wait. We wait here even if no Caddy or DNS is requested
|
// we keep the user informed during this wait. We wait here even if no Caddy or DNS is requested
|
||||||
// as the cluster needs to be ready so that commands such as 'uc machine ls' work immediately after init.
|
// as the cluster needs to be ready so that commands such as 'uc machine ls' work immediately after init.
|
||||||
err = spinner.New().
|
err = tui.RunSpinner(ctx, "Waiting for the cluster to be ready...", func(ctx context.Context) error {
|
||||||
Title(" Waiting for the cluster to be ready...").
|
return clusterClient.WaitClusterReady(ctx, 1*time.Minute)
|
||||||
Type(spinner.MiniDot).
|
})
|
||||||
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
|
|
||||||
return &spinner.Styles{
|
|
||||||
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
|
|
||||||
Title: lipgloss.NewStyle(),
|
|
||||||
}
|
|
||||||
})).
|
|
||||||
ActionWithErr(func(ctx context.Context) error {
|
|
||||||
return client.WaitClusterReady(ctx, 1*time.Minute)
|
|
||||||
}).
|
|
||||||
Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wait for cluster to be ready: %w", err)
|
return fmt.Errorf("wait for cluster to be ready: %w", err)
|
||||||
}
|
}
|
||||||
@@ -231,7 +245,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
if !opts.noDNS {
|
if !opts.noDNS {
|
||||||
domain, err := client.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.dnsEndpoint})
|
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.dnsEndpoint})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reserve cluster domain in Uncloud DNS: %w", err)
|
return fmt.Errorf("reserve cluster domain in Uncloud DNS: %w", err)
|
||||||
}
|
}
|
||||||
@@ -239,7 +253,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !opts.noCaddy {
|
if !opts.noCaddy {
|
||||||
d, err := client.NewCaddyDeployment("", "", api.Placement{})
|
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create caddy deployment: %w", err)
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
}
|
}
|
||||||
@@ -255,7 +269,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return caddy.UpdateDomainRecords(ctx, client, uncli.ProgressOut())
|
return caddy.UpdateDomainRecords(ctx, clusterClient, uncli.ProgressOut())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -3,10 +3,13 @@ package machine
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/logs"
|
"github.com/psviderski/uncloud/internal/cli/logs"
|
||||||
"github.com/psviderski/uncloud/internal/journal"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -18,14 +21,14 @@ func NewLogsCommand() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "logs [SERVICE...]",
|
Use: "logs [SERVICE...]",
|
||||||
Aliases: []string{"log"},
|
Aliases: []string{"log"},
|
||||||
Short: "View systemd service logs.",
|
Short: "View system service logs.",
|
||||||
Long: `View logs from the specified systemd service(s) across all machines in the cluster.
|
Long: `View logs from the specified system service(s) across all machines in the cluster.
|
||||||
Use -m to restrict to specific machines.
|
Use -m to restrict to specific machines.
|
||||||
|
|
||||||
Supported services:
|
Supported services:
|
||||||
uncloud the Uncloud daemon
|
corrosion the Corrosion distributed state store
|
||||||
docker the Docker daemon
|
docker the Docker daemon
|
||||||
uncloud-corrosion the Corrosion distributed state store
|
uncloud the Uncloud daemon
|
||||||
|
|
||||||
If no services are specified, streams logs from the uncloud service.`,
|
If no services are specified, streams logs from the uncloud service.`,
|
||||||
Example: ` # View recent logs for the uncloud service.
|
Example: ` # View recent logs for the uncloud service.
|
||||||
@@ -36,7 +39,7 @@ If no services are specified, streams logs from the uncloud service.`,
|
|||||||
uc machine logs -f uncloud
|
uc machine logs -f uncloud
|
||||||
|
|
||||||
# View logs from multiple services.
|
# View logs from multiple services.
|
||||||
uc machine logs uncloud docker uncloud-corrosion
|
uc machine logs uncloud docker corrosion
|
||||||
|
|
||||||
# Show last 20 lines per machine (default is 100).
|
# Show last 20 lines per machine (default is 100).
|
||||||
uc machine logs -n 20 docker
|
uc machine logs -n 20 docker
|
||||||
@@ -48,7 +51,7 @@ If no services are specified, streams logs from the uncloud service.`,
|
|||||||
uc machine logs --since 3h --until 1h30m docker
|
uc machine logs --since 3h --until 1h30m docker
|
||||||
|
|
||||||
# View logs only from specific machines.
|
# View logs only from specific machines.
|
||||||
uc machine logs -m machine1,machine2 uncloud uncloud-corrosion`,
|
uc machine logs -m machine1,machine2 uncloud corrosion`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return runLogs(cmd.Context(), uncli, args, options)
|
return runLogs(cmd.Context(), uncli, args, options)
|
||||||
@@ -56,24 +59,30 @@ If no services are specified, streams logs from the uncloud service.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLogs(ctx context.Context, uncli *cli.CLI, units []string, opts logs.Options) error {
|
func runLogs(ctx context.Context, uncli *cli.CLI, services []string, opts logs.Options) error {
|
||||||
if len(units) == 0 {
|
since, until, err := logs.TimeRange(opts.Since, opts.Until, time.Now())
|
||||||
units = []string{journal.UnitUncloud}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, unit := range units {
|
|
||||||
if !journal.ValidUnit(unit) {
|
|
||||||
return fmt.Errorf("invalid systemd service '%s'", unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tail, err := logs.Tail(opts.Tail)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
tail, err := opts.TailLines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(services) == 0 {
|
||||||
|
services = []string{api.SystemServiceUncloud}
|
||||||
|
}
|
||||||
|
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, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c, err := uncli.ConnectCluster(ctx)
|
c, err := uncli.ConnectCluster(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -84,8 +93,8 @@ func runLogs(ctx context.Context, uncli *cli.CLI, units []string, opts logs.Opti
|
|||||||
logsOpts := api.ServiceLogsOptions{
|
logsOpts := api.ServiceLogsOptions{
|
||||||
Follow: opts.Follow,
|
Follow: opts.Follow,
|
||||||
Tail: tail,
|
Tail: tail,
|
||||||
Since: opts.Since,
|
Since: since,
|
||||||
Until: opts.Until,
|
Until: until,
|
||||||
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,27 +110,27 @@ func runLogs(ctx context.Context, uncli *cli.CLI, units []string, opts logs.Opti
|
|||||||
machineNames = append(machineNames, m.Machine.Name)
|
machineNames = append(machineNames, m.Machine.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect one log stream per unit. MachineLogs merges across machines internally.
|
// Collect one log stream per service. MachineLogs merges across machines internally.
|
||||||
unitStreams := make([]<-chan api.ServiceLogEntry, 0, len(units))
|
serviceStreams := make([]<-chan api.ServiceLogEntry, 0, len(services))
|
||||||
for _, unit := range units {
|
for _, service := range services {
|
||||||
ch, err := c.MachineLogs(ctx, unit, logsOpts)
|
ch, err := c.MachineLogs(ctx, service, logsOpts)
|
||||||
if err != nil {
|
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
|
var stream <-chan api.ServiceLogEntry
|
||||||
if len(unitStreams) == 1 {
|
if len(serviceStreams) == 1 {
|
||||||
stream = unitStreams[0]
|
stream = serviceStreams[0]
|
||||||
} else {
|
} else {
|
||||||
// Each MachineLogs stream already runs its own inner merger with stall detection,
|
// Each MachineLogs stream already runs its own inner merger with stall detection,
|
||||||
// so the outer merger across units skips it to avoid duplicate warnings.
|
// so the outer merger across services skips it to avoid duplicate warnings.
|
||||||
merger := client.NewLogMerger(unitStreams, client.LogMergerOptions{})
|
merger := client.NewLogMerger(serviceStreams, client.LogMergerOptions{})
|
||||||
stream = merger.Stream()
|
stream = merger.Stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
formatter := logs.NewFormatter(machineNames, units, opts.UTC)
|
formatter := logs.NewFormatter(machineNames, services, opts.UTC)
|
||||||
|
|
||||||
// Print merged logs.
|
// Print merged logs.
|
||||||
for entry := range stream {
|
for entry := range stream {
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/logs"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunLogsInvalidTimeFilters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Invalid filters must fail before connecting to the cluster.
|
||||||
|
for _, flag := range []string{"since", "until"} {
|
||||||
|
opts := logs.Options{}
|
||||||
|
if flag == "since" {
|
||||||
|
opts.Since = "invalid"
|
||||||
|
} else {
|
||||||
|
opts.Until = "invalid"
|
||||||
|
}
|
||||||
|
err := runLogs(context.Background(), nil, nil, opts)
|
||||||
|
require.ErrorContains(t, err, "invalid --"+flag+" value")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@ package machine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
@@ -13,19 +15,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewListCommand() *cobra.Command {
|
func NewListCommand() *cobra.Command {
|
||||||
|
var output string
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls",
|
Use: "ls",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"list"},
|
||||||
Short: "List machines in a cluster.",
|
Short: "List machines in a cluster.",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return list(cmd.Context(), uncli)
|
return list(cmd.Context(), uncli, output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&output, "output", "o", "",
|
||||||
|
"Output format: 'json' or empty for a human-readable table.")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(ctx context.Context, uncli *cli.CLI) 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)
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
@@ -37,9 +49,19 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
return fmt.Errorf("list machines: %w", err)
|
return fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if output == "json" {
|
||||||
|
data, err := json.MarshalIndent(machines.ToNative(), "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal machines: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Print the list of machines in a table format.
|
// Print the list of machines in a table format.
|
||||||
t := tui.NewTable()
|
t := tui.NewTable()
|
||||||
t.Headers("NAME", "STATE", "ADDRESS", "PUBLIC IP", "WIREGUARD ENDPOINTS", "MACHINE ID")
|
t.Headers("NAME", "STATE", "ADDRESS", "PUBLIC IP", "WIREGUARD ENDPOINTS",
|
||||||
|
"OS", "KERNEL", "ARCH", "DOCKER", "VERSION")
|
||||||
|
|
||||||
for _, member := range machines {
|
for _, member := range machines {
|
||||||
m := member.Machine
|
m := member.Machine
|
||||||
@@ -58,17 +80,46 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
endpoints[i] = addrPort.String()
|
endpoints[i] = addrPort.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arch := "-"
|
||||||
|
if m.Arch != "" {
|
||||||
|
arch = m.Arch
|
||||||
|
}
|
||||||
|
|
||||||
|
osName := "-"
|
||||||
|
if m.OsPrettyName != "" {
|
||||||
|
osName = m.OsPrettyName
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel := "-"
|
||||||
|
if m.KernelVersion != "" {
|
||||||
|
kernel = m.KernelVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
daemonVersion := "-"
|
||||||
|
if m.DaemonVersion != "" {
|
||||||
|
daemonVersion = m.DaemonVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerVersion := "-"
|
||||||
|
if m.DockerVersion != "" {
|
||||||
|
dockerVersion = m.DockerVersion
|
||||||
|
}
|
||||||
|
|
||||||
t.Row(
|
t.Row(
|
||||||
m.Name,
|
m.Name,
|
||||||
capitalise(member.State.String()),
|
capitalise(member.State.String()),
|
||||||
subnet.String(),
|
subnet.String(),
|
||||||
publicIP,
|
publicIP,
|
||||||
strings.Join(endpoints, tui.Faint.Render(", ")),
|
strings.Join(endpoints, tui.Faint.Render(", ")),
|
||||||
member.Machine.Id,
|
osName,
|
||||||
|
kernel,
|
||||||
|
arch,
|
||||||
|
dockerVersion,
|
||||||
|
daemonVersion,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
lipgloss.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,10 +13,10 @@ import (
|
|||||||
"charm.land/lipgloss/v2/tree"
|
"charm.land/lipgloss/v2/tree"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/psviderski/uncloud/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -63,15 +63,16 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Verify the machine exists and list all service containers on it including stopped ones.
|
// Verify the machine exists in the cluster.
|
||||||
mctx, machines, err := client.ProxyMachinesContext(ctx, []string{nameOrID})
|
member, err := client.InspectMachine(ctx, nameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("inspect machine '%s': %w", nameOrID, err)
|
||||||
}
|
}
|
||||||
if len(machines) == 0 {
|
m := member.Machine
|
||||||
return fmt.Errorf("machine '%s' not found in the cluster", nameOrID)
|
|
||||||
}
|
// Create a proxy context for the machine being removed.
|
||||||
m := machines[0].Machine
|
// 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.
|
// Verify if the machine being removed is the proxy machine we're connected to.
|
||||||
proxyMachine, err := client.MachineClient.Inspect(ctx, nil)
|
proxyMachine, err := client.MachineClient.Inspect(ctx, nil)
|
||||||
@@ -103,7 +104,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
if reset {
|
if reset {
|
||||||
// Check if the machine is up and has service containers.
|
// Check if the machine is up and has service containers.
|
||||||
listOpts := container.ListOptions{All: true}
|
listOpts := container.ListOptions{All: true}
|
||||||
machineContainers, err := client.Docker.ListServiceContainers(mctx, "", listOpts)
|
machineContainers, err := client.Docker.ListServiceContainers(rmCtx, "", listOpts)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reachable = true
|
reachable = true
|
||||||
containers = machineContainers[0].Containers
|
containers = machineContainers[0].Containers
|
||||||
@@ -113,7 +114,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
plural = "s"
|
plural = "s"
|
||||||
}
|
}
|
||||||
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
|
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
|
||||||
fmt.Println(formatContainerTree(containers))
|
lipgloss.Println(formatContainerTree(containers))
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("This will remove all service containers from the machine, remove it from the cluster, " +
|
fmt.Println("This will remove all service containers from the machine, remove it from the cluster, " +
|
||||||
"and reset it to the uninitialised state.")
|
"and reset it to the uninitialised state.")
|
||||||
@@ -135,8 +136,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
return fmt.Errorf("confirm removal: %w", err)
|
return fmt.Errorf("confirm removal: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
fmt.Println("Cancelled. Machine was not removed.")
|
return cli.Cancelled("Cancelled. Machine was not removed.")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,20 +150,22 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = client.RemoveMachine(ctx, &pb.RemoveMachineRequest{Id: m.Id}); err != nil {
|
// Initiate reset before removing the machine from the cluster to stop it from updating the cluster store.
|
||||||
return fmt.Errorf("remove machine from cluster: %w", err)
|
// This is still optimistic as Reset only triggers the reset process that runs asynchronously.
|
||||||
}
|
|
||||||
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
|
|
||||||
|
|
||||||
if reset && reachable {
|
if reset && reachable {
|
||||||
_, err = client.MachineClient.Reset(mctx, &pb.ResetRequest{})
|
_, err = client.MachineClient.Reset(rmCtx, &pb.ResetRequest{})
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
fmt.Println("Machine reset initiated and will complete in the background.")
|
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.
|
// Remove the connection to the machine from the uncloud config if it exists.
|
||||||
if uncli.Config != nil {
|
if uncli.Config != nil {
|
||||||
contextName := uncli.ContextOverrideOrCurrent()
|
contextName := uncli.ContextOverrideOrCurrent()
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -38,10 +39,7 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Setup context to proxy request to all machines.
|
// Setup context to proxy request to all machines.
|
||||||
ctx, _, err = client.ProxyMachinesContext(ctx, nil)
|
ctx = client.ProxyMachinesContext(ctx, nil)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setup proxy context: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
resp, err := client.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -51,6 +49,15 @@ func rtt(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
// Map machine IDs to names for display from the response.
|
// Map machine IDs to names for display from the response.
|
||||||
machineNames := make(map[string]string)
|
machineNames := make(map[string]string)
|
||||||
for _, m := range resp.Machines {
|
for _, m := range resp.Machines {
|
||||||
|
// NOTE: Metadata should never be nil in practice. This is legacy fallback that will be removed.
|
||||||
|
if m.Metadata == nil {
|
||||||
|
tui.PrintWarning("metadata is missing in response from unknown server")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := m.Metadata.Err(); err != nil {
|
||||||
|
tui.PrintWarning(fmt.Sprintf("failed to inspect machine '%s': %s", m.Metadata.MachineName, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
if m.Machine == nil {
|
if m.Machine == nil {
|
||||||
continue
|
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))
|
t.Row(r.machine, r.peer, tui.FormatRTT(r.median), formatRTTStdDev(r.stdDev))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
lipgloss.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -70,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 "+
|
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
|
||||||
"WireGuard connections\n"+
|
"WireGuard connections\n"+
|
||||||
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is %d if omitted.\n", 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.",
|
"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()
|
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)
|
machine, err := client.InspectMachine(ctx, machineNameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("find machine: %w", err)
|
return fmt.Errorf("find machine: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the update request
|
req := &pb.UpdateMachineRequest{}
|
||||||
req := &pb.UpdateMachineRequest{
|
|
||||||
MachineId: machine.Machine.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.name != "" {
|
if opts.name != "" {
|
||||||
req.Name = &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.
|
// Parse and set endpoints if the flag was explicitly provided.
|
||||||
if cmd.Flags().Changed("wg-endpoint") {
|
if cmd.Flags().Changed("wg-endpoint") {
|
||||||
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
expanded := cli.ExpandCommaSeparatedValues(opts.wgEndpoints)
|
||||||
endpoints, err := cli.ParseWireGuardEndpoints(expanded)
|
endpoints, err := cli.ParseWireGuardEndpoints(expanded, network.DefaultWireGuardPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -131,8 +129,7 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
|
|||||||
req.Endpoints = endpoints
|
req.Endpoints = endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the update operation
|
updatedMachine, err := client.UpdateMachine(ctx, machine.Machine.Id, req)
|
||||||
updatedMachine, err := client.UpdateMachine(ctx, req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("update machine: %w", err)
|
return fmt.Errorf("update machine: %w", err)
|
||||||
}
|
}
|
||||||
@@ -8,22 +8,23 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
cmdcontext "github.com/psviderski/uncloud/cmd/uc/context"
|
||||||
cmdcontext "github.com/psviderski/uncloud/cmd/uncloud/context"
|
"github.com/psviderski/uncloud/cmd/uc/dns"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/dns"
|
"github.com/psviderski/uncloud/cmd/uc/image"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/image"
|
cmdmachine "github.com/psviderski/uncloud/cmd/uc/machine"
|
||||||
cmdmachine "github.com/psviderski/uncloud/cmd/uncloud/machine"
|
"github.com/psviderski/uncloud/cmd/uc/service"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/service"
|
"github.com/psviderski/uncloud/cmd/uc/volume"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/volume"
|
"github.com/psviderski/uncloud/cmd/uc/wg"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/wg"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/internal/fs"
|
"github.com/psviderski/uncloud/internal/fs"
|
||||||
"github.com/psviderski/uncloud/internal/log"
|
"github.com/psviderski/uncloud/internal/log"
|
||||||
"github.com/psviderski/uncloud/internal/machine"
|
"github.com/psviderski/uncloud/internal/machine"
|
||||||
"github.com/psviderski/uncloud/internal/version"
|
"github.com/psviderski/uncloud/internal/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type globalOptions struct {
|
type globalOptions struct {
|
||||||
@@ -39,10 +40,16 @@ func main() {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "uc",
|
Use: "uc",
|
||||||
Short: "A CLI tool for managing Uncloud resources such as machines, services, and volumes.",
|
Short: "A CLI tool for managing Uncloud resources such as machines, services, and volumes.",
|
||||||
Version: version.String(),
|
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Shell completion runs through the hidden __complete command which has flag parsing disabled,
|
||||||
|
// so the global flags from the completed command line are never parsed. Apply them manually to make
|
||||||
|
// completion work with --connect, --context, and --uncloud-config.
|
||||||
|
if cmd.Name() == cobra.ShellCompRequestCmd {
|
||||||
|
applyGlobalFlagsFromCompletionArgs(cmd.Root().PersistentFlags(), os.Args[1:])
|
||||||
|
}
|
||||||
|
|
||||||
cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT")
|
cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT")
|
||||||
cli.BindEnvToFlag(cmd, "context", "UNCLOUD_CONTEXT")
|
cli.BindEnvToFlag(cmd, "context", "UNCLOUD_CONTEXT")
|
||||||
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
|
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
|
||||||
@@ -81,10 +88,11 @@ func main() {
|
|||||||
|
|
||||||
configPath := fs.ExpandHomeDir(opts.configPath)
|
configPath := fs.ExpandHomeDir(opts.configPath)
|
||||||
|
|
||||||
|
// Make uc connect via the local Unix socket when running on a cluster machine.
|
||||||
if opts.connect == "" {
|
if opts.connect == "" {
|
||||||
if !fs.Exists(configPath) && fs.Exists(machine.DefaultUncloudSockPath) {
|
if !fs.Exists(configPath) && fs.Exists(machine.DefaultClusterAPISockPath) {
|
||||||
conn = &config.MachineConnection{
|
conn = &config.MachineConnection{
|
||||||
Unix: machine.DefaultUncloudSockPath,
|
Unix: machine.DefaultClusterAPISockPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,15 +121,11 @@ func main() {
|
|||||||
defaultHelpFunc(c, args)
|
defaultHelpFunc(c, args)
|
||||||
// Only show links for the root 'uc' command.
|
// Only show links for the root 'uc' command.
|
||||||
if c.Name() == "uc" {
|
if c.Name() == "uc" {
|
||||||
urlStyle := lipgloss.NewStyle().
|
|
||||||
Underline(true).
|
|
||||||
Foreground(lipgloss.Color("12")) // light blue
|
|
||||||
|
|
||||||
fmt.Fprintln(c.OutOrStdout())
|
fmt.Fprintln(c.OutOrStdout())
|
||||||
fmt.Fprintf(c.OutOrStdout(), "Learn more about Uncloud: %s\n",
|
fmt.Fprintf(c.OutOrStdout(), "Learn more about Uncloud: %s\n",
|
||||||
urlStyle.Render("https://uncloud.run/docs"))
|
tui.URLStyle.Render(version.DocsURL))
|
||||||
fmt.Fprintf(c.OutOrStdout(), "Join our Discord community: %s\n",
|
fmt.Fprintf(c.OutOrStdout(), "Join our Discord community: %s\n",
|
||||||
urlStyle.Render("https://uncloud.run/discord"))
|
tui.URLStyle.Render(version.DiscordURL))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -136,6 +140,7 @@ func main() {
|
|||||||
NewDocsCommand(),
|
NewDocsCommand(),
|
||||||
NewImagesCommand(),
|
NewImagesCommand(),
|
||||||
NewPsCommand(),
|
NewPsCommand(),
|
||||||
|
NewProxyCommand(),
|
||||||
caddy.NewRootCommand(),
|
caddy.NewRootCommand(),
|
||||||
cmdcontext.NewRootCommand(),
|
cmdcontext.NewRootCommand(),
|
||||||
dns.NewRootCommand(),
|
dns.NewRootCommand(),
|
||||||
@@ -151,6 +156,7 @@ func main() {
|
|||||||
service.NewScaleCommand("service"),
|
service.NewScaleCommand("service"),
|
||||||
service.NewStartCommand("service"),
|
service.NewStartCommand("service"),
|
||||||
service.NewStopCommand("service"),
|
service.NewStopCommand("service"),
|
||||||
|
NewVersionCommand(),
|
||||||
volume.NewRootCommand(),
|
volume.NewRootCommand(),
|
||||||
wg.NewRootCommand(),
|
wg.NewRootCommand(),
|
||||||
)
|
)
|
||||||
@@ -162,3 +168,27 @@ func main() {
|
|||||||
cobra.CheckErr(err)
|
cobra.CheckErr(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyGlobalFlagsFromCompletionArgs parses the global flags from the raw arguments of a __complete command and applies
|
||||||
|
// the ones found to flags. The trailing word being completed, unknown flags, and positional arguments are ignored.
|
||||||
|
func applyGlobalFlagsFromCompletionArgs(flags *pflag.FlagSet, args []string) {
|
||||||
|
// The shell always passes the word being completed as the last argument, even if it's empty.
|
||||||
|
// Exclude it from parsing as its value may not be complete yet.
|
||||||
|
if len(args) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args = args[:len(args)-1]
|
||||||
|
|
||||||
|
fset := pflag.NewFlagSet("global", pflag.ContinueOnError)
|
||||||
|
fset.ParseErrorsAllowlist.UnknownFlags = true
|
||||||
|
fset.String("connect", "", "")
|
||||||
|
fset.StringP("context", "c", "", "")
|
||||||
|
fset.String("uncloud-config", "", "")
|
||||||
|
// Parsing an incomplete command line may fail, apply the flags parsed so far anyway.
|
||||||
|
_ = fset.Parse(args)
|
||||||
|
|
||||||
|
fset.Visit(func(f *pflag.Flag) {
|
||||||
|
// Setting the flag marks it as changed so it takes precedence over environment variables.
|
||||||
|
_ = flags.Set(f.Name, f.Value.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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
@@ -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 ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/huh/v2/spinner"
|
|
||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
@@ -83,20 +82,10 @@ func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
|||||||
defer clusterClient.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
var containers []containerInfo
|
var containers []containerInfo
|
||||||
err = spinner.New().
|
err = tui.RunSpinner(ctx, "Collecting container info...", func(ctx context.Context) error {
|
||||||
Title(" Collecting container info...").
|
containers, err = collectContainers(ctx, clusterClient)
|
||||||
Type(spinner.MiniDot).
|
return err
|
||||||
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
|
})
|
||||||
return &spinner.Styles{
|
|
||||||
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
|
|
||||||
Title: lipgloss.NewStyle(),
|
|
||||||
}
|
|
||||||
})).
|
|
||||||
ActionWithErr(func(ctx context.Context) error {
|
|
||||||
containers, err = collectContainers(ctx, clusterClient)
|
|
||||||
return err
|
|
||||||
}).
|
|
||||||
Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("collect containers: %w", err)
|
return fmt.Errorf("collect containers: %w", err)
|
||||||
}
|
}
|
||||||
@@ -193,23 +182,12 @@ func printContainers(containers []containerInfo) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
lipgloss.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo, error) {
|
func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo, error) {
|
||||||
listCtx, machines, err := cli.ProxyMachinesContext(ctx, nil)
|
listCtx := client.ProxyMachinesContext(ctx, nil)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("proxy machines context: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a map of IP to machine name for resolving response metadata
|
|
||||||
machinesNamesByIP := make(map[string]string)
|
|
||||||
for _, m := range machines {
|
|
||||||
if addr, err := m.Machine.Network.ManagementIp.ToAddr(); err == nil {
|
|
||||||
machinesNamesByIP[addr.String()] = m.Machine.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all service containers across all machines in the cluster.
|
// List all service containers across all machines in the cluster.
|
||||||
machineContainers, err := cli.Docker.ListServiceContainers(
|
machineContainers, err := cli.Docker.ListServiceContainers(
|
||||||
@@ -221,29 +199,17 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
|
|||||||
|
|
||||||
var containers []containerInfo
|
var containers []containerInfo
|
||||||
for _, msc := range machineContainers {
|
for _, msc := range machineContainers {
|
||||||
// Metadata can be nil if the request was broadcasted to only one machine.
|
// NOTE: Metadata should never be nil in practice. This is legacy fallback that will be removed.
|
||||||
if msc.Metadata == nil && len(machineContainers) > 1 {
|
if msc.Metadata == nil {
|
||||||
return nil, fmt.Errorf("something went wrong with gRPC proxy: metadata is missing for a machine response")
|
tui.PrintWarning("metadata is missing in response from unknown server")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
machineName := "unknown"
|
machineName := msc.Metadata.MachineName
|
||||||
if msc.Metadata != nil {
|
|
||||||
var ok bool
|
|
||||||
machineName, ok = machinesNamesByIP[msc.Metadata.Machine]
|
|
||||||
if !ok {
|
|
||||||
// Fallback to machine's IP as name.
|
|
||||||
machineName = msc.Metadata.Machine
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback to the first available machine name.
|
|
||||||
if len(machines) > 0 {
|
|
||||||
machineName = machines[0].Machine.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if msc.Metadata != nil && msc.Metadata.Error != "" {
|
if err := msc.Metadata.Err(); err != nil {
|
||||||
tui.PrintWarning(fmt.Sprintf("failed to list containers on machine %s: %s", machineName,
|
tui.PrintWarning(fmt.Sprintf("failed to list service containers on machine %s: %s",
|
||||||
msc.Metadata.Error))
|
machineName, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
@@ -54,18 +56,19 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
return fmt.Errorf("inspect service: %w", err)
|
return fmt.Errorf("inspect service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
machines, err := client.ListMachines(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("list machines: %w", err)
|
|
||||||
}
|
|
||||||
machinesNamesByID := make(map[string]string)
|
|
||||||
for _, m := range machines {
|
|
||||||
machinesNamesByID[m.Machine.Id] = m.Machine.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Service ID: %s\n", svc.ID)
|
fmt.Printf("Service ID: %s\n", svc.ID)
|
||||||
fmt.Printf("Name: %s\n", svc.Name)
|
fmt.Printf("Name: %s\n", svc.Name)
|
||||||
fmt.Printf("Mode: %s\n", svc.Mode)
|
fmt.Printf("Mode: %s\n", svc.Mode)
|
||||||
|
|
||||||
|
spec := svc.Spec()
|
||||||
|
if len(spec.Labels) > 0 {
|
||||||
|
fmt.Println("Labels:")
|
||||||
|
printLabels(spec.Labels)
|
||||||
|
}
|
||||||
|
if len(spec.Container.Labels) > 0 {
|
||||||
|
fmt.Println("Container labels:")
|
||||||
|
printLabels(spec.Container.Labels)
|
||||||
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
// Combine regular and hook containers.
|
// Combine regular and hook containers.
|
||||||
@@ -97,7 +100,7 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
for _, ctr := range allContainers {
|
for _, ctr := range allContainers {
|
||||||
created := units.HumanDuration(now.Sub(createdTimes[ctr.Container.ID])) + " ago"
|
created := units.HumanDuration(now.Sub(createdTimes[ctr.Container.ID])) + " ago"
|
||||||
|
|
||||||
machine := machinesNamesByID[ctr.MachineID]
|
machine := ctr.MachineName
|
||||||
if machine == "" {
|
if machine == "" {
|
||||||
machine = ctr.MachineID
|
machine = ctr.MachineID
|
||||||
}
|
}
|
||||||
@@ -135,6 +138,13 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
lipgloss.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printLabels prints the labels sorted by key followed by a blank line.
|
||||||
|
func printLabels(labels map[string]string) {
|
||||||
|
for _, key := range slices.Sorted(maps.Keys(labels)) {
|
||||||
|
lipgloss.Println(" " + key + tui.Faint.Render("=") + labels[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
mapset "github.com/deckarep/golang-set/v2"
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
@@ -59,7 +60,7 @@ If no services are specified, streams logs from all services defined in the Comp
|
|||||||
uc logs -m machine1,machine2 web api`,
|
uc logs -m machine1,machine2 web api`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return runLogs(cmd.Context(), uncli, args, options)
|
return RunLogs(cmd.Context(), uncli, args, options)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
@@ -74,12 +75,19 @@ If no services are specified, streams logs from all services defined in the Comp
|
|||||||
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
||||||
completion.MachinesFlag(cmd)
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
completion.MachinesFlag(cmd)
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Options) error {
|
func RunLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Options) error {
|
||||||
|
since, until, err := logs.TimeRange(opts.Since, opts.Until, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tail, err := opts.TailLines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
serviceArgs, err := logs.ParseServiceArgs(args)
|
serviceArgs, err := logs.ParseServiceArgs(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -108,12 +116,6 @@ func runLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Optio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse tail option.
|
|
||||||
tail, err := logs.Tail(opts.Tail)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := uncli.ConnectCluster(ctx)
|
c, err := uncli.ConnectCluster(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
@@ -123,8 +125,8 @@ func runLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Optio
|
|||||||
baseOpts := api.ServiceLogsOptions{
|
baseOpts := api.ServiceLogsOptions{
|
||||||
Follow: opts.Follow,
|
Follow: opts.Follow,
|
||||||
Tail: tail,
|
Tail: tail,
|
||||||
Since: opts.Since,
|
Since: since,
|
||||||
Until: opts.Until,
|
Until: until,
|
||||||
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/logs"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunLogsInvalidTimeFilters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Invalid filters must fail before loading Compose files or connecting to the cluster.
|
||||||
|
for _, flag := range []string{"since", "until"} {
|
||||||
|
opts := logs.Options{}
|
||||||
|
if flag == "since" {
|
||||||
|
opts.Since = "invalid"
|
||||||
|
} else {
|
||||||
|
opts.Until = "invalid"
|
||||||
|
}
|
||||||
|
err := RunLogs(context.Background(), nil, nil, opts)
|
||||||
|
require.ErrorContains(t, err, "invalid --"+flag+" value")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
@@ -82,6 +83,6 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
t.Row(row...)
|
t.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
lipgloss.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@ func NewRootCommand() *cobra.Command {
|
|||||||
NewScaleCommand(""),
|
NewScaleCommand(""),
|
||||||
NewStartCommand(""),
|
NewStartCommand(""),
|
||||||
NewStopCommand(""),
|
NewStopCommand(""),
|
||||||
|
NewUpdateCommand(),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -21,13 +21,16 @@ import (
|
|||||||
type runOptions struct {
|
type runOptions struct {
|
||||||
caddyfile string
|
caddyfile string
|
||||||
command []string
|
command []string
|
||||||
|
containerLabels []string
|
||||||
cpu dockeropts.NanoCPUs
|
cpu dockeropts.NanoCPUs
|
||||||
entrypoint string
|
entrypoint string
|
||||||
entrypointChanged bool
|
entrypointChanged bool
|
||||||
env []string
|
env []string
|
||||||
image string
|
image string
|
||||||
|
labels []string
|
||||||
machines []string
|
machines []string
|
||||||
memory dockeropts.MemBytes
|
memory dockeropts.MemBytes
|
||||||
|
memoryReservation dockeropts.MemBytes
|
||||||
mode string
|
mode string
|
||||||
shmSize dockeropts.MemBytes
|
shmSize dockeropts.MemBytes
|
||||||
name string
|
name string
|
||||||
@@ -64,6 +67,9 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
cmd.Flags().StringVar(&opts.caddyfile, "caddyfile", "",
|
cmd.Flags().StringVar(&opts.caddyfile, "caddyfile", "",
|
||||||
"Path to a custom Caddy config (Caddyfile) for the service. "+
|
"Path to a custom Caddy config (Caddyfile) for the service. "+
|
||||||
"Cannot be used together with non-@host published ports.")
|
"Cannot be used together with non-@host published ports.")
|
||||||
|
cmd.Flags().StringArrayVar(&opts.containerLabels, "container-label", nil,
|
||||||
|
"Set a Docker label on service containers. Can be specified multiple times.\n"+
|
||||||
|
"Format: key=value or just key for an empty value.")
|
||||||
cmd.Flags().VarP(&opts.cpu, "cpu", "",
|
cmd.Flags().VarP(&opts.cpu, "cpu", "",
|
||||||
"Maximum number of CPU cores a service container can use. Fractional values are allowed: "+
|
"Maximum number of CPU cores a service container can use. Fractional values are allowed: "+
|
||||||
"0.5 for half a core or 2.25 for two and a quarter cores.")
|
"0.5 for half a core or 2.25 for two and a quarter cores.")
|
||||||
@@ -72,6 +78,9 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
cmd.Flags().StringSliceVarP(&opts.env, "env", "e", nil,
|
cmd.Flags().StringSliceVarP(&opts.env, "env", "e", nil,
|
||||||
"Set an environment variable for service containers. Can be specified multiple times.\n"+
|
"Set an environment variable for service containers. Can be specified multiple times.\n"+
|
||||||
"Format: VAR=value or just VAR to use the value from the local environment.")
|
"Format: VAR=value or just VAR to use the value from the local environment.")
|
||||||
|
cmd.Flags().StringArrayVarP(&opts.labels, "label", "l", nil,
|
||||||
|
"Set a service label. Service labels aren't applied to Docker containers, use --container-label for that.\n"+
|
||||||
|
"Can be specified multiple times. Format: key=value or just key for an empty value.")
|
||||||
cmd.Flags().StringVar(&opts.mode, "mode", api.ServiceModeReplicated,
|
cmd.Flags().StringVar(&opts.mode, "mode", api.ServiceModeReplicated,
|
||||||
fmt.Sprintf("Replication mode of the service: either '%s' (a specified number of containers across "+
|
fmt.Sprintf("Replication mode of the service: either '%s' (a specified number of containers across "+
|
||||||
"the machines) or '%s' (one container on every machine).",
|
"the machines) or '%s' (one container on every machine).",
|
||||||
@@ -83,6 +92,9 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
"Maximum amount of memory a service container can use. Value is a positive integer with optional unit suffix (b, k, m, g).\n"+
|
"Maximum amount of memory a service container can use. Value is a positive integer with optional unit suffix (b, k, m, g).\n"+
|
||||||
"Default unit is bytes if no suffix specified.\n"+
|
"Default unit is bytes if no suffix specified.\n"+
|
||||||
"Examples: 1073741824, 1024m, 1g (all equal 1 gibibyte)")
|
"Examples: 1073741824, 1024m, 1g (all equal 1 gibibyte)")
|
||||||
|
cmd.Flags().Var(&opts.memoryReservation, "memory-reservation",
|
||||||
|
"Minimum amount of memory reserved for a service container. Value is a positive integer with optional unit suffix (b, k, m, g).\n"+
|
||||||
|
"Default unit is bytes if no suffix specified.")
|
||||||
cmd.Flags().VarP(&opts.shmSize, "shm-size", "",
|
cmd.Flags().VarP(&opts.shmSize, "shm-size", "",
|
||||||
"Maximum amount of shared memory (mounted at /dev/shm) a service container can use. Value is a positive integer\n"+
|
"Maximum amount of shared memory (mounted at /dev/shm) a service container can use. Value is a positive integer\n"+
|
||||||
"with optional unit suffix (b, k, m, g). Default unit is bytes if no suffix specified.\n"+
|
"with optional unit suffix (b, k, m, g). Default unit is bytes if no suffix specified.\n"+
|
||||||
@@ -93,15 +105,18 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
"Give extended privileges to service containers. This is a security risk and should be used with caution.")
|
"Give extended privileges to service containers. This is a security risk and should be used with caution.")
|
||||||
cmd.Flags().StringSliceVarP(&opts.publish, "publish", "p", nil,
|
cmd.Flags().StringSliceVarP(&opts.publish, "publish", "p", nil,
|
||||||
"Publish a service port to make it accessible outside the cluster. Can be specified multiple times.\n"+
|
"Publish a service port to make it accessible outside the cluster. Can be specified multiple times.\n"+
|
||||||
"Format: [hostname:]container_port[/protocol] or [host_ip:]host_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"+
|
"Supported protocols: tcp, udp, http, https. The protocol defaults to https if a hostname is specified,\n"+
|
||||||
"and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+
|
"otherwise tcp. http(s) ports without a hostname use 'service-name.cluster-domain' as the default hostname\n"+
|
||||||
|
"if a cluster domain is reserved.\n"+
|
||||||
"Examples:\n"+
|
"Examples:\n"+
|
||||||
" -p 8080/https Publish port 8080 as HTTPS via reverse proxy with default service-name.cluster-domain hostname\n"+
|
" -p 8080/https Publish port 8080 as HTTPS via reverse proxy with default service-name.cluster-domain hostname\n"+
|
||||||
" -p app.example.com:8080/https Publish port 8080 as HTTPS via reverse proxy with custom hostname\n"+
|
" -p app.example.com:8080 Publish port 8080 as HTTPS via reverse proxy with custom hostname\n"+
|
||||||
// TODO: add support for publishing L4 tcp/udp ports.
|
// TODO: add support for publishing L4 tcp/udp ports.
|
||||||
//" -p 9000:8080 Publish port 8080 as TCP port 9000 via reverse proxy\n"+
|
//" -p 9000:8080 Publish port 8080 as TCP port 9000 via reverse proxy\n"+
|
||||||
" -p 53:5353/udp@host Bind UDP port 5353 to host port 53")
|
" -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,
|
cmd.Flags().StringVar(&opts.pull, "pull", api.PullPolicyMissing,
|
||||||
fmt.Sprintf("Pull image from the registry before running service containers ('%s', '%s', '%s').",
|
fmt.Sprintf("Pull image from the registry before running service containers ('%s', '%s', '%s').",
|
||||||
api.PullPolicyAlways, api.PullPolicyMissing, api.PullPolicyNever))
|
api.PullPolicyAlways, api.PullPolicyMissing, api.PullPolicyNever))
|
||||||
@@ -120,6 +135,7 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
"Mount a data volume or host path into service containers. Service containers will be scheduled on the machine(s) where\n"+
|
"Mount a data volume or host path into service containers. Service containers will be scheduled on the machine(s) where\n"+
|
||||||
"the volume is located. Can be specified multiple times.\n"+
|
"the volume is located. Can be specified multiple times.\n"+
|
||||||
"Format: volume_name:/container/path[:ro|volume-nocopy] or /host/path:/container/path[:ro]\n"+
|
"Format: volume_name:/container/path[:ro|volume-nocopy] or /host/path:/container/path[:ro]\n"+
|
||||||
|
"Host and container paths support runtime templates such as {{.Container.Name}}.\n"+
|
||||||
"Examples:\n"+
|
"Examples:\n"+
|
||||||
" -v postgres-data:/var/lib/postgresql/data Mount volume 'postgres-data' to /var/lib/postgresql/data in container\n"+
|
" -v postgres-data:/var/lib/postgresql/data Mount volume 'postgres-data' to /var/lib/postgresql/data in container\n"+
|
||||||
" -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+
|
" -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+
|
||||||
@@ -189,6 +205,18 @@ func prepareServiceSpec(opts runOptions) (api.ServiceSpec, error) {
|
|||||||
return spec, err
|
return spec, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
labels, err := parseLabels(opts.labels)
|
||||||
|
if err != nil {
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
if err = api.ValidateLabels(labels); err != nil {
|
||||||
|
return spec, fmt.Errorf("invalid service labels: %w", err)
|
||||||
|
}
|
||||||
|
containerLabels, err := parseLabels(opts.containerLabels)
|
||||||
|
if err != nil {
|
||||||
|
return spec, err
|
||||||
|
}
|
||||||
|
|
||||||
switch opts.mode {
|
switch opts.mode {
|
||||||
case api.ServiceModeReplicated, api.ServiceModeGlobal:
|
case api.ServiceModeReplicated, api.ServiceModeGlobal:
|
||||||
default:
|
default:
|
||||||
@@ -229,17 +257,20 @@ func prepareServiceSpec(opts runOptions) (api.ServiceSpec, error) {
|
|||||||
Command: opts.command,
|
Command: opts.command,
|
||||||
Env: env,
|
Env: env,
|
||||||
Image: opts.image,
|
Image: opts.image,
|
||||||
|
Labels: containerLabels,
|
||||||
Privileged: opts.privileged,
|
Privileged: opts.privileged,
|
||||||
PullPolicy: opts.pull,
|
PullPolicy: opts.pull,
|
||||||
Resources: api.ContainerResources{
|
Resources: api.ContainerResources{
|
||||||
CPU: opts.cpu.Value(),
|
CPU: opts.cpu.Value(),
|
||||||
Memory: opts.memory.Value(),
|
Memory: opts.memory.Value(),
|
||||||
SharedMemory: opts.shmSize.Value(),
|
MemoryReservation: opts.memoryReservation.Value(),
|
||||||
Ulimits: ulimits,
|
SharedMemory: opts.shmSize.Value(),
|
||||||
|
Ulimits: ulimits,
|
||||||
},
|
},
|
||||||
User: opts.user,
|
User: opts.user,
|
||||||
VolumeMounts: mounts,
|
VolumeMounts: mounts,
|
||||||
},
|
},
|
||||||
|
Labels: labels,
|
||||||
Mode: opts.mode,
|
Mode: opts.mode,
|
||||||
Name: opts.name,
|
Name: opts.name,
|
||||||
Placement: placement,
|
Placement: placement,
|
||||||
@@ -298,6 +329,24 @@ func parseEnv(env []string) (api.EnvVars, error) {
|
|||||||
return envVars, nil
|
return envVars, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseLabels parses labels from the command line arguments in the format "key=value" or just "key" for an empty value.
|
||||||
|
func parseLabels(labels []string) (map[string]string, error) {
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]string, len(labels))
|
||||||
|
for _, label := range labels {
|
||||||
|
key, value, _ := strings.Cut(label, "=")
|
||||||
|
if strings.TrimSpace(key) == "" {
|
||||||
|
return nil, fmt.Errorf("invalid label '%s': key must not be empty", label)
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseVolumeFlags parses volume flag values in Docker CLI format and returns VolumeSpecs and VolumeMounts.
|
// parseVolumeFlags parses volume flag values in Docker CLI format and returns VolumeSpecs and VolumeMounts.
|
||||||
// It handles both named volumes (volume_name:/container/path[:ro|volume-nocopy])
|
// It handles both named volumes (volume_name:/container/path[:ro|volume-nocopy])
|
||||||
// and bind mounts (/host/path:/container/path[:ro]).
|
// and bind mounts (/host/path:/container/path[:ro]).
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// validRunOptions returns the minimal options that produce a valid service spec.
|
||||||
|
func validRunOptions() runOptions {
|
||||||
|
return runOptions{
|
||||||
|
image: "nginx:1.27",
|
||||||
|
name: "web",
|
||||||
|
mode: api.ServiceModeReplicated,
|
||||||
|
pull: api.PullPolicyMissing,
|
||||||
|
replicas: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLabels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
labels, err := parseLabels(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Nil(t, labels)
|
||||||
|
|
||||||
|
labels, err = parseLabels([]string{"tier=frontend", "empty", "list=a,b", "expr=a=b", "tier=backend"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]string{
|
||||||
|
"tier": "backend",
|
||||||
|
"empty": "",
|
||||||
|
"list": "a,b",
|
||||||
|
"expr": "a=b",
|
||||||
|
}, labels)
|
||||||
|
|
||||||
|
_, err = parseLabels([]string{"=value"})
|
||||||
|
require.ErrorContains(t, err, "key must not be empty")
|
||||||
|
|
||||||
|
_, err = parseLabels([]string{" \t =value"})
|
||||||
|
require.ErrorContains(t, err, "key must not be empty")
|
||||||
|
|
||||||
|
labels, err = parseLabels([]string{"key=value with spaces"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]string{"key": "value with spaces"}, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareServiceSpec(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("minimal options", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(validRunOptions())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "web", spec.Name)
|
||||||
|
assert.Equal(t, api.ServiceModeReplicated, spec.Mode)
|
||||||
|
assert.EqualValues(t, 1, spec.Replicas)
|
||||||
|
assert.Equal(t, "nginx:1.27", spec.Container.Image)
|
||||||
|
assert.Equal(t, api.PullPolicyMissing, spec.Container.PullPolicy)
|
||||||
|
assert.Nil(t, spec.Caddy)
|
||||||
|
assert.Nil(t, spec.Labels)
|
||||||
|
assert.Nil(t, spec.Container.Labels)
|
||||||
|
assert.Nil(t, spec.Container.Entrypoint)
|
||||||
|
assert.Nil(t, spec.Container.Resources.Ulimits)
|
||||||
|
assert.Nil(t, spec.Placement.Machines)
|
||||||
|
assert.Empty(t, spec.Container.Env)
|
||||||
|
assert.Empty(t, spec.Ports)
|
||||||
|
assert.Empty(t, spec.Volumes)
|
||||||
|
assert.Empty(t, spec.Container.VolumeMounts)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("all options", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.command = []string{"nginx", "-g", "daemon off;"}
|
||||||
|
opts.env = []string{"FOO=bar", "EMPTY=", "EXPR=a=b"}
|
||||||
|
opts.labels = []string{"tier=frontend"}
|
||||||
|
opts.containerLabels = []string{"logging=enabled"}
|
||||||
|
opts.machines = []string{"m1,m2", "m3"}
|
||||||
|
opts.privileged = true
|
||||||
|
opts.pull = api.PullPolicyAlways
|
||||||
|
opts.publish = []string{"app.example.com:8080/https", "53:5353/udp@host"}
|
||||||
|
opts.replicas = 3
|
||||||
|
opts.ulimits = []string{"nofile=1024:2048", "nproc=65535"}
|
||||||
|
opts.user = "1000:1000"
|
||||||
|
opts.volumes = []string{"data:/var/lib/data:ro"}
|
||||||
|
require.NoError(t, opts.cpu.Set("0.5"))
|
||||||
|
require.NoError(t, opts.memory.Set("128m"))
|
||||||
|
require.NoError(t, opts.memoryReservation.Set("32m"))
|
||||||
|
require.NoError(t, opts.shmSize.Set("64m"))
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, api.ServiceSpec{
|
||||||
|
Container: api.ContainerSpec{
|
||||||
|
Command: []string{"nginx", "-g", "daemon off;"},
|
||||||
|
Env: api.EnvVars{"FOO": "bar", "EMPTY": "", "EXPR": "a=b"},
|
||||||
|
Image: "nginx:1.27",
|
||||||
|
Labels: map[string]string{"logging": "enabled"},
|
||||||
|
Privileged: true,
|
||||||
|
PullPolicy: api.PullPolicyAlways,
|
||||||
|
Resources: api.ContainerResources{
|
||||||
|
CPU: 500 * api.MilliCore,
|
||||||
|
Memory: 128 * units.MiB,
|
||||||
|
MemoryReservation: 32 * units.MiB,
|
||||||
|
SharedMemory: 64 * units.MiB,
|
||||||
|
Ulimits: map[string]api.Ulimit{
|
||||||
|
"nofile": {Soft: 1024, Hard: 2048},
|
||||||
|
"nproc": {Soft: 65535, Hard: 65535},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
User: "1000:1000",
|
||||||
|
VolumeMounts: []api.VolumeMount{
|
||||||
|
{VolumeName: "data", ContainerPath: "/var/lib/data", ReadOnly: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Labels: map[string]string{"tier": "frontend"},
|
||||||
|
Mode: api.ServiceModeReplicated,
|
||||||
|
Name: "web",
|
||||||
|
Placement: api.Placement{Machines: []string{"m1", "m2", "m3"}},
|
||||||
|
Ports: []api.PortSpec{
|
||||||
|
{
|
||||||
|
Hostname: "app.example.com",
|
||||||
|
ContainerPort: 8080,
|
||||||
|
Protocol: api.ProtocolHTTPS,
|
||||||
|
Mode: api.PortModeIngress,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PublishedPort: 53,
|
||||||
|
ContainerPort: 5353,
|
||||||
|
Protocol: api.ProtocolUDP,
|
||||||
|
Mode: api.PortModeHost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Replicas: 3,
|
||||||
|
Volumes: []api.VolumeSpec{
|
||||||
|
{
|
||||||
|
Name: "data",
|
||||||
|
Type: api.VolumeTypeVolume,
|
||||||
|
VolumeOptions: &api.VolumeOptions{Name: "data"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, spec)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("generates a name when not specified", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.name = ""
|
||||||
|
opts.image = "bitnami/nginx:latest"
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Regexp(t, `^nginx-[a-z0-9]{4}$`, spec.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("keeps the specified name", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.name = "custom"
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "custom", spec.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("global mode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.mode = api.ServiceModeGlobal
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, api.ServiceModeGlobal, spec.Mode)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("entrypoint", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.entrypoint = "/bin/sh"
|
||||||
|
opts.entrypointChanged = true
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{"/bin/sh"}, spec.Container.Entrypoint)
|
||||||
|
|
||||||
|
// An explicitly empty entrypoint resets the one from the image.
|
||||||
|
opts = validRunOptions()
|
||||||
|
opts.entrypointChanged = true
|
||||||
|
spec, err = prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{""}, spec.Container.Entrypoint)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("env without a value is omitted when not set locally", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.env = []string{"UNCLOUD_TEST_ENV_THAT_IS_NOT_SET"}
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotContains(t, spec.Container.Env, "UNCLOUD_TEST_ENV_THAT_IS_NOT_SET")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bind mount", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.volumes = []string{"/data/uploads:/app/uploads"}
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, spec.Volumes, 1)
|
||||||
|
assert.Regexp(t, `^bind-[a-z0-9]{4}$`, spec.Volumes[0].Name)
|
||||||
|
assert.Equal(t, api.VolumeTypeBind, spec.Volumes[0].Type)
|
||||||
|
require.NotNil(t, spec.Volumes[0].BindOptions)
|
||||||
|
assert.Equal(t, "/data/uploads", spec.Volumes[0].BindOptions.HostPath)
|
||||||
|
assert.True(t, spec.Volumes[0].BindOptions.CreateHostPath)
|
||||||
|
|
||||||
|
require.Len(t, spec.Container.VolumeMounts, 1)
|
||||||
|
assert.Equal(t, spec.Volumes[0].Name, spec.Container.VolumeMounts[0].VolumeName)
|
||||||
|
assert.Equal(t, "/app/uploads", spec.Container.VolumeMounts[0].ContainerPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("named volume mounted twice creates one volume", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.volumes = []string{"data:/a", "data:/b"}
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, spec.Volumes, 1)
|
||||||
|
assert.Len(t, spec.Container.VolumeMounts, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("caddyfile content is trimmed", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
path := filepath.Join(t.TempDir(), "Caddyfile")
|
||||||
|
require.NoError(t, os.WriteFile(path, []byte("\nexample.com {\n}\n\n"), 0o600))
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.caddyfile = path
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, spec.Caddy)
|
||||||
|
assert.Equal(t, "example.com {\n}", spec.Caddy.Config)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty caddyfile is ignored", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
path := filepath.Join(t.TempDir(), "Caddyfile")
|
||||||
|
require.NoError(t, os.WriteFile(path, []byte(" \n"), 0o600))
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.caddyfile = path
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Nil(t, spec.Caddy)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("caddyfile can be used with host ports", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
path := filepath.Join(t.TempDir(), "Caddyfile")
|
||||||
|
require.NoError(t, os.WriteFile(path, []byte("example.com {\n}"), 0o600))
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.caddyfile = path
|
||||||
|
opts.publish = []string{"53:5353/udp@host"}
|
||||||
|
|
||||||
|
_, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareServiceSpec_LocalEnv(t *testing.T) {
|
||||||
|
// Not parallel because t.Setenv changes the environment of the whole process.
|
||||||
|
t.Setenv("UNCLOUD_TEST_LOCAL_ENV", "from-local")
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
opts.env = []string{"UNCLOUD_TEST_LOCAL_ENV"}
|
||||||
|
|
||||||
|
spec, err := prepareServiceSpec(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "from-local", spec.Container.Env["UNCLOUD_TEST_LOCAL_ENV"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrepareServiceSpec_Invalid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
caddyfile := filepath.Join(t.TempDir(), "Caddyfile")
|
||||||
|
require.NoError(t, os.WriteFile(caddyfile, []byte("example.com {\n}"), 0o600))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
modify func(opts *runOptions)
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "invalid image",
|
||||||
|
modify: func(opts *runOptions) { opts.image = "INVALID IMAGE" },
|
||||||
|
wantErr: "invalid image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid name",
|
||||||
|
modify: func(opts *runOptions) { opts.name = "Not_Valid" },
|
||||||
|
wantErr: "invalid service name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid mode",
|
||||||
|
modify: func(opts *runOptions) { opts.mode = "unknown" },
|
||||||
|
wantErr: "invalid replication mode: 'unknown'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid pull policy",
|
||||||
|
modify: func(opts *runOptions) { opts.pull = "sometimes" },
|
||||||
|
wantErr: "invalid pull policy: 'sometimes'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid env",
|
||||||
|
modify: func(opts *runOptions) { opts.env = []string{"=value"} },
|
||||||
|
wantErr: "invalid environment variable: '=value'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid port",
|
||||||
|
modify: func(opts *runOptions) { opts.publish = []string{"not-a-port"} },
|
||||||
|
wantErr: "invalid service port 'not-a-port'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid ulimit",
|
||||||
|
modify: func(opts *runOptions) { opts.ulimits = []string{"nofile"} },
|
||||||
|
wantErr: "invalid ulimit 'nofile'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "volume without a container path",
|
||||||
|
modify: func(opts *runOptions) { opts.volumes = []string{"data"} },
|
||||||
|
wantErr: "invalid volume mount 'data'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "volume with a relative container path",
|
||||||
|
modify: func(opts *runOptions) { opts.volumes = []string{"data:relative"} },
|
||||||
|
wantErr: "must be absolute path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "volume with an unknown option",
|
||||||
|
modify: func(opts *runOptions) { opts.volumes = []string{"data:/data:unknown"} },
|
||||||
|
wantErr: "invalid option: 'unknown'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing caddyfile",
|
||||||
|
modify: func(opts *runOptions) { opts.caddyfile = filepath.Join(os.TempDir(), "uncloud-test-no-such-file") },
|
||||||
|
wantErr: "read Caddyfile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "caddyfile with an ingress port",
|
||||||
|
modify: func(opts *runOptions) {
|
||||||
|
opts.caddyfile = caddyfile
|
||||||
|
opts.publish = []string{"app.example.com:8080/https"}
|
||||||
|
},
|
||||||
|
wantErr: "cannot be specified simultaneously",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service label without a key",
|
||||||
|
modify: func(opts *runOptions) { opts.labels = []string{"=value"} },
|
||||||
|
wantErr: "key must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "container label without a key",
|
||||||
|
modify: func(opts *runOptions) { opts.containerLabels = []string{"=value"} },
|
||||||
|
wantErr: "key must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service label with whitespace",
|
||||||
|
modify: func(opts *runOptions) { opts.labels = []string{"my label=value"} },
|
||||||
|
wantErr: "invalid service labels: label 'my label' must not contain whitespace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "container label with whitespace",
|
||||||
|
modify: func(opts *runOptions) { opts.containerLabels = []string{"my label=value"} },
|
||||||
|
wantErr: "invalid container labels: label 'my label' must not contain whitespace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reserved service label",
|
||||||
|
modify: func(opts *runOptions) { opts.labels = []string{api.LabelManaged + "=true"} },
|
||||||
|
wantErr: "invalid service labels",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reserved container label",
|
||||||
|
modify: func(opts *runOptions) { opts.containerLabels = []string{api.LabelManaged + "=true"} },
|
||||||
|
wantErr: "invalid container labels",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := validRunOptions()
|
||||||
|
tt.modify(&opts)
|
||||||
|
|
||||||
|
_, err := prepareServiceSpec(opts)
|
||||||
|
require.ErrorContains(t, err, tt.wantErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,9 +30,11 @@ func NewScaleCommand(groupID string) *cobra.Command {
|
|||||||
Short: "Scale a replicated service by changing the number of replicas.",
|
Short: "Scale a replicated service by changing the number of replicas.",
|
||||||
Long: "Scale a replicated service by changing the number of replicas.",
|
Long: "Scale a replicated service by changing the number of replicas.",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
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, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
opts.service = args[0]
|
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.
|
// Ask for confirmation unless auto-confirmed with --yes.
|
||||||
if !opts.yes {
|
if !opts.yes {
|
||||||
if !tui.IsStdinTerminal() {
|
if !tui.IsTerminalAvailable() {
|
||||||
return errors.New("cannot ask to confirm scaling plan in non-interactive mode, " +
|
return errors.New("cannot ask to confirm scaling plan in non-interactive mode, " +
|
||||||
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
dockeropts "github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updateOptions struct {
|
||||||
|
service string
|
||||||
|
|
||||||
|
cpu dockeropts.NanoCPUs
|
||||||
|
cpuChanged bool
|
||||||
|
memory dockeropts.MemBytes
|
||||||
|
memoryChanged bool
|
||||||
|
memoryReservation dockeropts.MemBytes
|
||||||
|
reservationChanged bool
|
||||||
|
labelAdd []string
|
||||||
|
labelRm []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateCommand() *cobra.Command {
|
||||||
|
opts := updateOptions{}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "update SERVICE",
|
||||||
|
Short: "Update a service in place without recreating its containers.",
|
||||||
|
Long: `Update service configuration in place without recreating its containers.
|
||||||
|
|
||||||
|
A CPU or memory limit can be changed but not removed. To remove a limit or change anything else,
|
||||||
|
redeploy the service with 'uc deploy'`,
|
||||||
|
Example: ` # Change the CPU and memory limits.
|
||||||
|
uc service update web --cpu 0.5 --memory 512m
|
||||||
|
|
||||||
|
# Add or change a service label and remove another one.
|
||||||
|
uc service update web --label-add tier=frontend --label-rm legacy`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
|
opts.service = args[0]
|
||||||
|
opts.cpuChanged = cmd.Flag("cpu").Changed
|
||||||
|
opts.memoryChanged = cmd.Flag("memory").Changed
|
||||||
|
opts.reservationChanged = cmd.Flag("memory-reservation").Changed
|
||||||
|
|
||||||
|
return update(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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Var(&opts.cpu, "cpu",
|
||||||
|
"Maximum number of CPU cores a service container can use.\n"+
|
||||||
|
"Fractional values are allowed: 0.5 for half a core or 2.25 for two and a quarter cores.")
|
||||||
|
cmd.Flags().Var(&opts.memory, "memory",
|
||||||
|
"Maximum amount of memory a service container can use. Value is a positive integer with\n"+
|
||||||
|
"optional unit suffix (b, k, m, g). Default unit is bytes if no suffix specified.\n"+
|
||||||
|
"Examples: 1073741824, 1024m, 1g (all equal 1 gibibyte)")
|
||||||
|
cmd.Flags().Var(&opts.memoryReservation, "memory-reservation",
|
||||||
|
"Minimum amount of memory reserved for a service container. Value is a positive integer with\n"+
|
||||||
|
"optional unit suffix (b, k, m, g). Default unit is bytes if no suffix specified.")
|
||||||
|
cmd.Flags().StringArrayVar(&opts.labelAdd, "label-add", nil,
|
||||||
|
"Add or change a service label. Can be specified multiple times.\n"+
|
||||||
|
"Format: key=value or just key for an empty value.")
|
||||||
|
cmd.Flags().StringArrayVar(&opts.labelRm, "label-rm", nil,
|
||||||
|
"Remove a service label by its key. Can be specified multiple times.")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(ctx context.Context, uncli *cli.CLI, opts updateOptions) error {
|
||||||
|
if !opts.cpuChanged && !opts.memoryChanged && !opts.reservationChanged &&
|
||||||
|
len(opts.labelAdd) == 0 && len(opts.labelRm) == 0 {
|
||||||
|
return errors.New("no changes specified, set at least one of the flags: " +
|
||||||
|
"--cpu, --memory, --memory-reservation, --label-add, --label-rm")
|
||||||
|
}
|
||||||
|
|
||||||
|
labelAdd, err := parseLabels(opts.labelAdd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = api.ValidateLabels(labelAdd); err != nil {
|
||||||
|
return fmt.Errorf("invalid service labels: %w", err)
|
||||||
|
}
|
||||||
|
for _, key := range opts.labelRm {
|
||||||
|
if _, ok := labelAdd[key]; ok {
|
||||||
|
return fmt.Errorf("label '%s' cannot be both added and removed", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("inspect service '%s': %w", opts.service, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all containers before changing any of them to avoid a partially updated service when a limit can't be
|
||||||
|
// changed in place. Containers may have different specs after a failed deployment so each one is updated from
|
||||||
|
// its own spec.
|
||||||
|
updates := make(map[string]api.UpdateContainerOptions, len(svc.Containers))
|
||||||
|
for _, mc := range svc.Containers {
|
||||||
|
spec := applyServiceUpdate(mc.Container.ServiceSpec, opts, labelAdd)
|
||||||
|
|
||||||
|
resources := mc.Container.AppliedResources()
|
||||||
|
if err = resources.ValidateUpdateInPlace(spec.Container.Resources); err != nil {
|
||||||
|
return fmt.Errorf("container '%s' on machine '%s' can't be updated without recreating it: %w",
|
||||||
|
stringid.TruncateID(mc.Container.ID), mc.MachineName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerOpts := deploy.ContainerUpdateOptions(mc.Container, spec)
|
||||||
|
if containerOpts != (api.UpdateContainerOptions{}) {
|
||||||
|
updates[mc.Container.ID] = containerOpts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updates) == 0 {
|
||||||
|
fmt.Printf("Service %s is already up to date. No changes required.\n", tui.NameStyle.Render(svc.Name))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
|
// Keep updating the remaining containers if one fails, for example, because its machine is down.
|
||||||
|
// Running the command again only updates the containers that still differ.
|
||||||
|
var errs []error
|
||||||
|
for id, containerOpts := range updates {
|
||||||
|
if err := clusterClient.UpdateContainer(ctx, svc.ID, id, containerOpts); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("update container '%s': %w", stringid.TruncateID(id), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}, uncli.ProgressOut(), "Updating service "+tui.NameStyle.Render(svc.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyServiceUpdate returns a copy of the spec with the requested changes applied.
|
||||||
|
func applyServiceUpdate(spec api.ServiceSpec, opts updateOptions, labelAdd map[string]string) api.ServiceSpec {
|
||||||
|
updated := spec.Clone()
|
||||||
|
|
||||||
|
if opts.cpuChanged {
|
||||||
|
updated.Container.Resources.CPU = opts.cpu.Value()
|
||||||
|
}
|
||||||
|
if opts.memoryChanged {
|
||||||
|
updated.Container.Resources.Memory = opts.memory.Value()
|
||||||
|
}
|
||||||
|
if opts.reservationChanged {
|
||||||
|
updated.Container.Resources.MemoryReservation = opts.memoryReservation.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(labelAdd) > 0 && updated.Labels == nil {
|
||||||
|
updated.Labels = make(map[string]string, len(labelAdd))
|
||||||
|
}
|
||||||
|
maps.Copy(updated.Labels, labelAdd)
|
||||||
|
|
||||||
|
for key := range updated.Labels {
|
||||||
|
if slices.Contains(opts.labelRm, key) {
|
||||||
|
delete(updated.Labels, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplyServiceUpdate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
newSpec := func() api.ServiceSpec {
|
||||||
|
return api.ServiceSpec{
|
||||||
|
Name: "web",
|
||||||
|
Container: api.ContainerSpec{
|
||||||
|
Image: "nginx",
|
||||||
|
Labels: map[string]string{"container": "keep"},
|
||||||
|
Resources: api.ContainerResources{
|
||||||
|
CPU: 100 * api.MilliCore,
|
||||||
|
Memory: 1024,
|
||||||
|
MemoryReservation: 512,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Labels: map[string]string{"tier": "frontend", "legacy": "true"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("adds, changes and removes labels", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
spec := newSpec()
|
||||||
|
updated := applyServiceUpdate(spec, updateOptions{labelRm: []string{"legacy", "missing"}},
|
||||||
|
map[string]string{"tier": "backend", "team": "core"})
|
||||||
|
|
||||||
|
assert.Equal(t, map[string]string{"tier": "backend", "team": "core"}, updated.Labels)
|
||||||
|
assert.Equal(t, map[string]string{"container": "keep"}, updated.Container.Labels)
|
||||||
|
assert.Equal(t, spec.Container.Resources, updated.Container.Resources)
|
||||||
|
assert.Equal(t, map[string]string{"tier": "frontend", "legacy": "true"}, spec.Labels,
|
||||||
|
"Expected the original spec to stay unchanged")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("adds labels to a spec without labels", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
spec := newSpec()
|
||||||
|
spec.Labels = nil
|
||||||
|
updated := applyServiceUpdate(spec, updateOptions{}, map[string]string{"tier": "backend"})
|
||||||
|
|
||||||
|
assert.Equal(t, map[string]string{"tier": "backend"}, updated.Labels)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("changes only requested limits", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := updateOptions{cpuChanged: true}
|
||||||
|
require.NoError(t, opts.cpu.Set("0.5"))
|
||||||
|
|
||||||
|
updated := applyServiceUpdate(newSpec(), opts, nil)
|
||||||
|
|
||||||
|
assert.EqualValues(t, 500*api.MilliCore, updated.Container.Resources.CPU)
|
||||||
|
assert.EqualValues(t, 1024, updated.Container.Resources.Memory)
|
||||||
|
assert.EqualValues(t, 512, updated.Container.Resources.MemoryReservation)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("sends only changed fields to the container", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := updateOptions{memoryChanged: true}
|
||||||
|
require.NoError(t, opts.memory.Set("2048"))
|
||||||
|
|
||||||
|
ctr := api.ServiceContainer{ServiceSpec: newSpec()}
|
||||||
|
containerOpts := deploy.ContainerUpdateOptions(ctr, applyServiceUpdate(ctr.ServiceSpec, opts, nil))
|
||||||
|
|
||||||
|
require.NotNil(t, containerOpts.Memory)
|
||||||
|
assert.EqualValues(t, 2048, *containerOpts.Memory)
|
||||||
|
assert.Nil(t, containerOpts.CPU)
|
||||||
|
assert.Nil(t, containerOpts.MemoryReservation)
|
||||||
|
assert.Nil(t, containerOpts.ServiceLabels)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("removing the last label clears service labels", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctr := api.ServiceContainer{ServiceSpec: newSpec()}
|
||||||
|
containerOpts := deploy.ContainerUpdateOptions(ctr, applyServiceUpdate(
|
||||||
|
ctr.ServiceSpec, updateOptions{labelRm: []string{"tier", "legacy"}}, nil,
|
||||||
|
))
|
||||||
|
|
||||||
|
require.NotNil(t, containerOpts.ServiceLabels)
|
||||||
|
assert.Empty(t, *containerOpts.ServiceLabels)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no changes produce empty container options", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctr := api.ServiceContainer{ServiceSpec: newSpec()}
|
||||||
|
containerOpts := deploy.ContainerUpdateOptions(ctr, applyServiceUpdate(
|
||||||
|
ctr.ServiceSpec, updateOptions{}, map[string]string{"tier": "frontend"},
|
||||||
|
))
|
||||||
|
|
||||||
|
assert.Equal(t, api.UpdateContainerOptions{}, containerOpts)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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"
|
"charm.land/huh/v2"
|
||||||
"github.com/docker/docker/api/types/volume"
|
"github.com/docker/docker/api/types/volume"
|
||||||
|
"github.com/psviderski/uncloud/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
@@ -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)
|
t.Row(v.Volume.Name, v.Volume.Driver, v.MachineName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
lipgloss.Println(t)
|
||||||
return nil
|
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)
|
return fmt.Errorf("confirm removal: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
fmt.Println("Cancelled. No volumes were removed.")
|
return cli.Cancelled("Cancelled. No volumes were removed.")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user