mirror of
https://github.com/psviderski/uncloud.git
synced 2026-10-06 21:29:02 +00:00
Compare commits
252
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
03ff4cd51d | ||
|
|
519825c34a | ||
|
|
eebb7b51ed | ||
|
|
5ecd09aa2b | ||
|
|
34560470ce | ||
|
|
dd68079005 | ||
|
|
d23f1bdc00 | ||
|
|
4503b17046 | ||
|
|
4a12217e65 | ||
|
|
e7a62f6908 | ||
|
|
3310b478a1 | ||
|
|
8384d4162c | ||
|
|
90af550693 | ||
|
|
fe829efbf5 | ||
|
|
cd07278858 | ||
|
|
45cf87a0cb | ||
|
|
9ce624facd | ||
|
|
c38d916dbb | ||
|
|
50057c42e0 | ||
|
|
f379bf4c14 | ||
|
|
dd3d809c6e | ||
|
|
44897f351f | ||
|
|
c888e0a76f | ||
|
|
7b554cd703 | ||
|
|
d340659370 | ||
|
|
6fb68c22c5 | ||
|
|
b1897ce719 | ||
|
|
47199c36ce | ||
|
|
d812cf8f6e | ||
|
|
39690eed72 | ||
|
|
56a54ab3fd | ||
|
|
303c8e4506 | ||
|
|
8afe52367f | ||
|
|
5b5cd44d68 | ||
|
|
19c13f6cd2 | ||
|
|
0f5f8727a3 | ||
|
|
2045819735 | ||
|
|
cb72f67560 | ||
|
|
b852d34068 | ||
|
|
7e1b91c372 | ||
|
|
6409a47c71 | ||
|
|
f44ada06b9 | ||
|
|
3a79aeffcc | ||
|
|
84990ad692 | ||
|
|
8d023f5c53 | ||
|
|
1c0d48cb46 | ||
|
|
4d5ca42a9d | ||
|
|
fdbffbf020 | ||
|
|
102421df19 | ||
|
|
f8234916cb | ||
|
|
208a561003 | ||
|
|
6e9acefbe6 | ||
|
|
39181708dd | ||
|
|
e8111a419f | ||
|
|
9cfcf49d28 | ||
|
|
60ff088971 | ||
|
|
3eda6cdc22 | ||
|
|
b1be80adf2 | ||
|
|
02a318cd2e | ||
|
|
f41590157f | ||
|
|
dd989a281e | ||
|
|
f60a9ff293 | ||
|
|
07e380b95f | ||
|
|
4e060914ec | ||
|
|
f317dde168 | ||
|
|
f2f6fee89a | ||
|
|
b3b33a82ba | ||
|
|
cd149e4506 | ||
|
|
eff1bc88d8 | ||
|
|
ab644f6a47 | ||
|
|
883f184e37 | ||
|
|
897f30fd36 | ||
|
|
943fea0515 | ||
|
|
e96cce88c1 | ||
|
|
1930bb5766 | ||
|
|
a844cc6f67 | ||
|
|
043b85ab70 | ||
|
|
fe813302b7 | ||
|
|
3e30a59d4c | ||
|
|
0da9df17a4 | ||
|
|
0e820f9c52 | ||
|
|
772b31b57f | ||
|
|
1c8b77054a | ||
|
|
cef047221c | ||
|
|
4b34c42b76 | ||
|
|
7ec8879af3 | ||
|
|
cb77d42107 | ||
|
|
d45b5a488e | ||
|
|
f4581eac11 | ||
|
|
0651b9d0ac | ||
|
|
62976f3338 | ||
|
|
d38312fec6 | ||
|
|
636307d3ea | ||
|
|
e22cdf0b18 | ||
|
|
9aee75ba28 | ||
|
|
37c937a570 | ||
|
|
a1bde30b57 | ||
|
|
06c2bc55f2 | ||
|
|
0a9a1db815 |
No files matched your search
@@ -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
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 525 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 635 KiB |
@@ -0,0 +1,99 @@
|
|||||||
|
name: Nightly Build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
MISE_VERSION: "2026.3.7"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nightly:
|
||||||
|
# Run on macOS to be able to build the fsevent dependency required by compose on macOS.
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Mise
|
||||||
|
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
|
||||||
|
with:
|
||||||
|
version: ${{ env.MISE_VERSION }}
|
||||||
|
- name: Reset changes in mise.lock after fresh mise install
|
||||||
|
run: git checkout mise.lock
|
||||||
|
|
||||||
|
- name: Cache Go build and module cache
|
||||||
|
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Caches/go-build
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}-${{ hashFiles('**/*.go') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}-
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Run GoReleaser snapshot
|
||||||
|
run: goreleaser release --snapshot --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Delete existing nightly release
|
||||||
|
run: gh release delete nightly --yes --cleanup-tag || true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create nightly release
|
||||||
|
run: |
|
||||||
|
COMMIT=$(git rev-parse --short HEAD)
|
||||||
|
DATE=$(date -u '+%Y-%m-%d %H:%M UTC')
|
||||||
|
RELEASE_NOTES=$(cat <<EOF
|
||||||
|
Built from \`main\` at ${COMMIT} on ${DATE}.
|
||||||
|
|
||||||
|
The commands below show how to install the nightly version of uncloud.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> These are nightly development builds and may be unstable.
|
||||||
|
|
||||||
|
**Install uncloud CLI:**
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
curl -fsS https://get.uncloud.run/install.sh | VERSION=nightly sh
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Initialize a cluster or add a machine:**
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
uc machine init --version nightly user@host
|
||||||
|
uc machine add --version nightly user@host
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Upgrade machine daemon:**
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
ARCH=\$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
|
||||||
|
curl -fsSL -o uncloudd.tar.gz https://github.com/psviderski/uncloud/releases/download/nightly/uncloudd_linux_\${ARCH}.tar.gz
|
||||||
|
tar -xf uncloudd.tar.gz
|
||||||
|
sudo install uncloudd /usr/local/bin/uncloudd
|
||||||
|
rm uncloudd uncloudd.tar.gz
|
||||||
|
sudo systemctl restart uncloud
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
gh release create nightly \
|
||||||
|
--title "Nightly Build" \
|
||||||
|
--notes "${RELEASE_NOTES}" \
|
||||||
|
--prerelease \
|
||||||
|
--target ${{ github.sha }} \
|
||||||
|
dist/*.tar.gz dist/checksums.txt
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
timeout-minutes: 30
|
||||||
@@ -44,3 +44,4 @@ node_modules/
|
|||||||
.devcontainer/
|
.devcontainer/
|
||||||
|
|
||||||
.zed/
|
.zed/
|
||||||
|
research/
|
||||||
+27
-10
@@ -11,10 +11,18 @@ before:
|
|||||||
|
|
||||||
report_sizes: true
|
report_sizes: true
|
||||||
|
|
||||||
|
git:
|
||||||
|
# Skip the stale 'nightly' tag so {{.Version}} resolves to the latest semver tag for --snapshot builds.
|
||||||
|
ignore_tags:
|
||||||
|
- nightly
|
||||||
|
snapshot:
|
||||||
|
# Semver-compliant nightly version, e.g. "0.19.0-nightly-abc1234" when the latest tag is v0.18.0.
|
||||||
|
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 }}
|
||||||
@@ -26,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
|
||||||
@@ -39,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
|
||||||
@@ -85,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ github.com/siderolabs/grpc-proxy // gRPC proxy for forwarding
|
|||||||
|
|
||||||
```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
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ uc context use <name> # Switch context
|
|||||||
- **Package naming**: Use clear, descriptive names
|
- **Package naming**: Use clear, descriptive names
|
||||||
- **Error handling**: Wrap errors with context using `fmt.Errorf`
|
- **Error handling**: Wrap errors with context using `fmt.Errorf`
|
||||||
- **Logging**: Use structured logging with levels
|
- **Logging**: Use structured logging with levels
|
||||||
- **gRPC**: Services defined in `internal/machine/api/pb/`
|
- **gRPC**: Services defined in `api/pb/`
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ uc context use <name> # Switch context
|
|||||||
- Unit tests alongside source files (`*_test.go`)
|
- Unit tests alongside source files (`*_test.go`)
|
||||||
- Integration tests in `test/e2e/`
|
- Integration tests in `test/e2e/`
|
||||||
- Test fixtures in `test/fixtures/`
|
- Test fixtures in `test/fixtures/`
|
||||||
- Use table driven tests whenever possible
|
- Use the `testify` library for assertions (e.g., `require.Equal`, `assert.Nil`)
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
@@ -238,7 +238,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
|
||||||
|
|||||||
+11
-7
@@ -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
|
||||||
|
|
||||||
@@ -46,7 +48,9 @@ RUN apk --no-cache add crane
|
|||||||
RUN crane pull --platform ${TARGETOS}/${TARGETARCH} "${CORROSION_IMAGE}" /corrosion.tar
|
RUN crane pull --platform ${TARGETOS}/${TARGETARCH} "${CORROSION_IMAGE}" /corrosion.tar
|
||||||
|
|
||||||
# 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:27.3.1-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 \
|
||||||
@@ -54,7 +58,7 @@ RUN apk --no-cache add \
|
|||||||
wireguard-tools
|
wireguard-tools
|
||||||
|
|
||||||
COPY --from=corrosion-image-tarball /corrosion.tar /images/corrosion.tar
|
COPY --from=corrosion-image-tarball /corrosion.tar /images/corrosion.tar
|
||||||
COPY scripts/docker/dind scripts/docker/entrypoint.sh /usr/local/bin/
|
COPY scripts/docker/entrypoint.sh /usr/local/bin/
|
||||||
COPY --from=uncloudd /build/uncloudd /usr/local/bin/
|
COPY --from=uncloudd /build/uncloudd /usr/local/bin/
|
||||||
|
|
||||||
ENTRYPOINT ["entrypoint.sh"]
|
ENTRYPOINT ["entrypoint.sh"]
|
||||||
|
|||||||
+7
-6
@@ -5,7 +5,8 @@ A short guide on setting up your local development environment.
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- [mise](https://mise.jdx.dev/) (development environment setup tool)
|
- [mise](https://mise.jdx.dev/) (development environment setup tool)
|
||||||
- [Docker](https://docs.docker.com/get-docker/) (for running end-to-end tests)
|
- [Docker](https://docs.docker.com/get-docker/) with BuildKit (for running end-to-end tests). Make sure the
|
||||||
|
[`docker buildx`](https://docs.docker.com/build/concepts/overview/) plugin is installed.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@@ -31,13 +32,13 @@ available outside the project, so they don't interfere with system packages.
|
|||||||
Build the CLI:
|
Build the CLI:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go build -o uc ./cmd/uncloud
|
go build -o uc ./cmd/uc
|
||||||
```
|
```
|
||||||
|
|
||||||
Or build and run the CLI with a single command:
|
Or build and run the CLI with a single command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go run ./cmd/uncloud --help
|
go run ./cmd/uc --help
|
||||||
```
|
```
|
||||||
|
|
||||||
The Uncloud daemon (`uncloudd`) only supports Linux, so you need to cross-compile it if you're developing on macOS or
|
The Uncloud daemon (`uncloudd`) only supports Linux, so you need to cross-compile it if you're developing on macOS or
|
||||||
@@ -106,7 +107,7 @@ make lint-and-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
|
||||||
@@ -123,8 +124,8 @@ make mocks
|
|||||||
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.
|
||||||
Each cluster machine runs as a Docker container connected to a shared Docker network.
|
Each cluster machine runs as a Docker container connected to a shared Docker network.
|
||||||
|
|
||||||
Cluster machines use the `ghcr.io/psviderski/ucind:latest` Docker image by default. It may not always have the
|
Cluster machines use the `ghcr.io/psviderski/ucind:latest` Docker image by default. It may not always have the latest
|
||||||
latest changes to the `uncloudd` daemon code as it's not automatically rebuilt on CI yet.
|
changes to the `uncloudd` daemon code as it's not automatically rebuilt on CI yet.
|
||||||
|
|
||||||
You can rebuild it locally to pick up the latest changes, including your work in progress, and test them in a UCinD
|
You can rebuild it locally to pick up the latest changes, including your work in progress, and test them in a UCinD
|
||||||
cluster:
|
cluster:
|
||||||
|
|||||||
@@ -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,11 +10,12 @@ 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:
|
||||||
@@ -70,4 +72,4 @@ _lint:
|
|||||||
|
|
||||||
.PHONY: cli-docs
|
.PHONY: cli-docs
|
||||||
cli-docs:
|
cli-docs:
|
||||||
go run ./cmd/uncloud docs
|
go run ./cmd/uc docs
|
||||||
@@ -44,18 +44,20 @@ complexity of Kubernetes.
|
|||||||
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
||||||
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
||||||
Encrypt.
|
Encrypt.
|
||||||
|
* **Cluster storage for TLS certificates**: Native cluster storage for Caddy shares certificates and ACME challenge
|
||||||
|
tokens across machines and coordinates issuance.
|
||||||
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
||||||
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
||||||
|
|
||||||
## 🎬 Quick demo
|
## 🎬 Quick demo
|
||||||
|
|
||||||
The screenshot below demonstrates how I use Uncloud to deploy https://uncloud.run website to 2 remote machines from
|
The screenshot below demonstrates how we use Uncloud to deploy https://uncloud.run website to 2 remote machines from
|
||||||
the [`compose.yaml`](website/compose.yaml) file on my local machine.
|
the [`compose.yaml`](website/compose.yaml) file on the local machine.
|
||||||
|
|
||||||
It exposes the container port `8000/tcp` as HTTPS on the domain `uncloud.run`, served by the Caddy reverse proxy on the
|
It exposes the container port `8000/tcp` as HTTPS on `uncloud.run` domain, served by the Caddy reverse proxy on the
|
||||||
remote machines. All managed by Uncloud.
|
remote machines. All managed by Uncloud.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Here is a more advanced use case. Deploy a highly available web app with automatic HTTPS across multiple regions and
|
Here is a more advanced use case. Deploy a highly available web app with automatic HTTPS across multiple regions and
|
||||||
on-premises in just a couple minutes.
|
on-premises in just a couple minutes.
|
||||||
@@ -102,6 +104,9 @@ platform, whether you're running on a $5 VPS, a spare Mac mini, or a rack of bar
|
|||||||
|
|
||||||
See [Installation](https://uncloud.run/docs/getting-started/install-cli) for more options.
|
See [Installation](https://uncloud.run/docs/getting-started/install-cli) for more options.
|
||||||
|
|
||||||
|
There's also a [nightly rolling release](https://github.com/psviderski/uncloud/releases/tag/nightly) available if you
|
||||||
|
want to try the latest features and fixes before they're included in an official release.
|
||||||
|
|
||||||
2. Initialise your first machine:
|
2. Initialise your first machine:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -151,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...
|
||||||
@@ -164,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! 🎉
|
||||||
@@ -187,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.
|
||||||
@@ -210,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! 🎉
|
||||||
@@ -243,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
|
||||||
@@ -283,18 +285,24 @@ machine is a full backup of your control plane.
|
|||||||
|
|
||||||
## 🧪 Interactive tutorials
|
## 🧪 Interactive tutorials
|
||||||
|
|
||||||
To give you a chance to play with Uncloud without even leaving your browser, we're providing interactive tutorials and playgrounds on the [iximiuz Labs](https://labs.iximiuz.com/) platform.
|
To give you a chance to play with Uncloud without even leaving your browser, we're providing interactive tutorials and
|
||||||
|
playgrounds on the [iximiuz Labs](https://labs.iximiuz.com/) platform.
|
||||||
|
|
||||||
Available tutorials:
|
Available tutorials:
|
||||||
|
|
||||||
1. [Setting up a new Uncloud cluster](https://labs.iximiuz.com/tutorials/uncloud-create-cluster-ebebf72b) - the tutorial walks you through creating a new cluster with two machines and then deploying a simple web service to it.
|
1. [Setting up a new Uncloud cluster](https://labs.iximiuz.com/tutorials/uncloud-create-cluster-ebebf72b) - the tutorial
|
||||||
|
walks you through creating a new cluster with two machines and then deploying a simple web service to it.
|
||||||
|
2. [Deploying a Django web application](https://labs.iximiuz.com/tutorials/uncloud-deploy-django-app-7a378bc3) - deploy
|
||||||
|
a Django web app from source code to a remote Linux server using Uncloud.
|
||||||
|
|
||||||
You can also launch the [Uncloud playground](https://labs.iximiuz.com/playgrounds/uncloud-cluster-64523f7c) where you can play with an already initialized Uncloud cluster.
|
You can also launch the [Uncloud playground](https://labs.iximiuz.com/playgrounds/uncloud-cluster-64523f7c) where you
|
||||||
|
can play with an already initialized Uncloud cluster.
|
||||||
|
|
||||||
## 🏗 Project status
|
## 🏗 Project status
|
||||||
|
|
||||||
Uncloud is currently in active development and is **not ready for production use**. Features may change significantly
|
Uncloud is under active development and pre-1.0, so expect occasional breaking changes between releases. The core build,
|
||||||
and there may be breaking changes between releases.
|
push, and deploy workflows are stable, and much of the runtime relies on battle-tested projects: Docker, Caddy,
|
||||||
|
WireGuard, and Corrosion.
|
||||||
|
|
||||||
We'd love your input! Here's how you can contribute:
|
We'd love your input! Here's how you can contribute:
|
||||||
|
|
||||||
@@ -317,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
|
||||||
@@ -332,8 +340,8 @@ SQLite database used to share Uncloud's cluster state.
|
|||||||
|
|
||||||
* Join our [Discord server](https://discord.gg/eR35KQJhPu) for real-time discussions, support, and updates.
|
* Join our [Discord server](https://discord.gg/eR35KQJhPu) for real-time discussions, support, and updates.
|
||||||
* Follow [@psviderski](https://x.com/psviderski) on X/Twitter.
|
* Follow [@psviderski](https://x.com/psviderski) on X/Twitter.
|
||||||
* Subscribe to [my newsletter](https://uncloud.run/#subscribe) to follow the progress, get early insights into new
|
* Subscribe to [our newsletter](https://uncloud.run/#subscribe) to follow the journey of building Uncloud: progress
|
||||||
features, and be the first to know when it's ready for production use.
|
updates, user stories, and the occasional deep dive.
|
||||||
* Watch this repository for releases.
|
* Watch this repository for releases.
|
||||||
|
|
||||||
## 💖 Sponsors
|
## 💖 Sponsors
|
||||||
@@ -351,8 +359,6 @@ These companies and projects are helping Uncloud with their generous sponsorship
|
|||||||
<img alt="Trendshift" src="https://trendshift.io/api/badge/repositories/14069" />
|
<img alt="Trendshift" src="https://trendshift.io/api/badge/repositories/14069" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Thank you [@cedws](https://github.com/cedws) for being the first contributor to Uncloud! 🎉
|
|
||||||
|
|
||||||
<a href="https://github.com/psviderski/uncloud/graphs/contributors">
|
<a href="https://github.com/psviderski/uncloud/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=psviderski/uncloud" />
|
<img src="https://contrib.rocks/image?repo=psviderski/uncloud" />
|
||||||
</a>
|
</a>
|
||||||
@@ -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",
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -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",
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -0,0 +1,786 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.34.2
|
||||||
|
// protoc v5.27.3
|
||||||
|
// source: api/pb/common.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
status "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
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 LogEntry_StreamType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogEntry_UNKNOWN LogEntry_StreamType = 0
|
||||||
|
LogEntry_STDOUT LogEntry_StreamType = 1
|
||||||
|
LogEntry_STDERR LogEntry_StreamType = 2
|
||||||
|
LogEntry_HEARTBEAT LogEntry_StreamType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for LogEntry_StreamType.
|
||||||
|
var (
|
||||||
|
LogEntry_StreamType_name = map[int32]string{
|
||||||
|
0: "UNKNOWN",
|
||||||
|
1: "STDOUT",
|
||||||
|
2: "STDERR",
|
||||||
|
3: "HEARTBEAT",
|
||||||
|
}
|
||||||
|
LogEntry_StreamType_value = map[string]int32{
|
||||||
|
"UNKNOWN": 0,
|
||||||
|
"STDOUT": 1,
|
||||||
|
"STDERR": 2,
|
||||||
|
"HEARTBEAT": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x LogEntry_StreamType) Enum() *LogEntry_StreamType {
|
||||||
|
p := new(LogEntry_StreamType)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x LogEntry_StreamType) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LogEntry_StreamType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_api_pb_common_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LogEntry_StreamType) Type() protoreflect.EnumType {
|
||||||
|
return &file_api_pb_common_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LogEntry_StreamType.Descriptor instead.
|
||||||
|
func (LogEntry_StreamType) EnumDescriptor() ([]byte, []int) {
|
||||||
|
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
|
||||||
|
// about the machine that responded to the request.
|
||||||
|
type Metadata struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// ID of the machine the response came from.
|
||||||
|
MachineId string `protobuf:"bytes,4,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"`
|
||||||
|
// Name of the machine the response came from.
|
||||||
|
MachineName string `protobuf:"bytes,5,opt,name=machine_name,json=machineName,proto3" json:"machine_name,omitempty"`
|
||||||
|
// Address of the machine the response came from.
|
||||||
|
MachineAddr string `protobuf:"bytes,1,opt,name=machine_addr,json=machineAddr,proto3" json:"machine_addr,omitempty"`
|
||||||
|
// error is set if the request to upstream failed. The rest of the response is undefined.
|
||||||
|
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
|
||||||
|
// error as a gRPC Status message.
|
||||||
|
Status *status.Status `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Metadata) Reset() {
|
||||||
|
*x = Metadata{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Metadata) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Metadata) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Metadata) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 Metadata.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Metadata) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Metadata) GetMachineId() string {
|
||||||
|
if x != nil {
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Metadata) GetError() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Error
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Metadata) GetStatus() *status.Status {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A helper message for marshalling the metadata field and injecting it into the response by the gRPC proxy.
|
||||||
|
type Empty struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Empty) Reset() {
|
||||||
|
*x = Empty{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Empty) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Empty) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Empty) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 Empty.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Empty) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Empty) GetMetadata() *Metadata {
|
||||||
|
if x != nil {
|
||||||
|
return x.Metadata
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyResponse is a response message to be returned by the gRPC proxy when a request to the upstream failed.
|
||||||
|
// The nested Empty.Metadata message should contain an error.
|
||||||
|
type EmptyResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Messages []*Empty `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EmptyResponse) Reset() {
|
||||||
|
*x = EmptyResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EmptyResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*EmptyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 EmptyResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*EmptyResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EmptyResponse) GetMessages() []*Empty {
|
||||||
|
if x != nil {
|
||||||
|
return x.Messages
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IP struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IP) Reset() {
|
||||||
|
*x = IP{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IP) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*IP) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *IP) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 IP.ProtoReflect.Descriptor instead.
|
||||||
|
func (*IP) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IP) GetIp() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPPort struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Ip *IP `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPort) Reset() {
|
||||||
|
*x = IPPort{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPort) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*IPPort) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *IPPort) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 IPPort.ProtoReflect.Descriptor instead.
|
||||||
|
func (*IPPort) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPort) GetIp() *IP {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPort) GetPort() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Port
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPPrefix struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Ip *IP `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||||
|
Bits uint32 `protobuf:"varint,2,opt,name=bits,proto3" json:"bits,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPrefix) Reset() {
|
||||||
|
*x = IPPrefix{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPrefix) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*IPPrefix) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *IPPrefix) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 IPPrefix.ProtoReflect.Descriptor instead.
|
||||||
|
func (*IPPrefix) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPrefix) GetIp() *IP {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *IPPrefix) GetBits() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Bits
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogsRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
// Options for logs retrieval.
|
||||||
|
Follow bool `protobuf:"varint,2,opt,name=follow,proto3" json:"follow,omitempty"`
|
||||||
|
Tail int32 `protobuf:"varint,3,opt,name=tail,proto3" json:"tail,omitempty"` // -1 means all
|
||||||
|
Since string `protobuf:"bytes,4,opt,name=since,proto3" json:"since,omitempty"` // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
Until string `protobuf:"bytes,5,opt,name=until,proto3" json:"until,omitempty"` // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) Reset() {
|
||||||
|
*x = LogsRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 LogsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetFollow() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Follow
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetTail() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tail
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetSince() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Since
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetUntil() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Until
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogEntry struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Stream LogEntry_StreamType `protobuf:"varint,1,opt,name=stream,proto3,enum=api.LogEntry_StreamType" json:"stream,omitempty"`
|
||||||
|
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||||
|
// Log line content. Empty for heartbeat entries.
|
||||||
|
Message []byte `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) Reset() {
|
||||||
|
*x = LogEntry{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_pb_common_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogEntry) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogEntry) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_pb_common_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 LogEntry.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogEntry) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_pb_common_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) GetStream() LogEntry_StreamType {
|
||||||
|
if x != nil {
|
||||||
|
return x.Stream
|
||||||
|
}
|
||||||
|
return LogEntry_UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) GetTimestamp() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.Timestamp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) GetMessage() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Message
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_api_pb_common_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_api_pb_common_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67,
|
||||||
|
0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||||
|
0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
|
||||||
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
|
||||||
|
0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e,
|
||||||
|
0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x61,
|
||||||
|
0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||||
|
0x6e, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x06,
|
||||||
|
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67,
|
||||||
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
|
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74,
|
||||||
|
0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||||
|
0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x37, 0x0a, 0x0d,
|
||||||
|
0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a,
|
||||||
|
0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||||
|
0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73,
|
||||||
|
0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||||
|
0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x22, 0x35, 0x0a, 0x06, 0x49,
|
||||||
|
0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12,
|
||||||
|
0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f,
|
||||||
|
0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x17,
|
||||||
|
0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69,
|
||||||
|
0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x22, 0x75, 0x0a, 0x0b, 0x4c,
|
||||||
|
0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f,
|
||||||
|
0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x6c,
|
||||||
|
0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
|
||||||
|
0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18,
|
||||||
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||||
|
0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6e, 0x74,
|
||||||
|
0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
|
||||||
|
0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||||
|
0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53,
|
||||||
|
0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61,
|
||||||
|
0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02,
|
||||||
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||||
|
0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d,
|
||||||
|
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65,
|
||||||
|
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54,
|
||||||
|
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
|
||||||
|
0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06,
|
||||||
|
0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41, 0x52,
|
||||||
|
0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x03, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||||
|
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69,
|
||||||
|
0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62,
|
||||||
|
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_api_pb_common_proto_rawDescOnce sync.Once
|
||||||
|
file_api_pb_common_proto_rawDescData = file_api_pb_common_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_api_pb_common_proto_rawDescGZIP() []byte {
|
||||||
|
file_api_pb_common_proto_rawDescOnce.Do(func() {
|
||||||
|
file_api_pb_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_common_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_api_pb_common_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_api_pb_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
|
var file_api_pb_common_proto_goTypes = []any{
|
||||||
|
(LogEntry_StreamType)(0), // 0: api.LogEntry.StreamType
|
||||||
|
(*Metadata)(nil), // 1: api.Metadata
|
||||||
|
(*Empty)(nil), // 2: api.Empty
|
||||||
|
(*EmptyResponse)(nil), // 3: api.EmptyResponse
|
||||||
|
(*IP)(nil), // 4: api.IP
|
||||||
|
(*IPPort)(nil), // 5: api.IPPort
|
||||||
|
(*IPPrefix)(nil), // 6: api.IPPrefix
|
||||||
|
(*LogsRequest)(nil), // 7: api.LogsRequest
|
||||||
|
(*LogEntry)(nil), // 8: api.LogEntry
|
||||||
|
(*status.Status)(nil), // 9: google.rpc.Status
|
||||||
|
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
||||||
|
}
|
||||||
|
var file_api_pb_common_proto_depIdxs = []int32{
|
||||||
|
9, // 0: api.Metadata.status:type_name -> google.rpc.Status
|
||||||
|
1, // 1: api.Empty.metadata:type_name -> api.Metadata
|
||||||
|
2, // 2: api.EmptyResponse.messages:type_name -> api.Empty
|
||||||
|
4, // 3: api.IPPort.ip:type_name -> api.IP
|
||||||
|
4, // 4: api.IPPrefix.ip:type_name -> api.IP
|
||||||
|
0, // 5: api.LogEntry.stream:type_name -> api.LogEntry.StreamType
|
||||||
|
10, // 6: api.LogEntry.timestamp:type_name -> google.protobuf.Timestamp
|
||||||
|
7, // [7:7] is the sub-list for method output_type
|
||||||
|
7, // [7:7] is the sub-list for method input_type
|
||||||
|
7, // [7:7] is the sub-list for extension type_name
|
||||||
|
7, // [7:7] is the sub-list for extension extendee
|
||||||
|
0, // [0:7] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_api_pb_common_proto_init() }
|
||||||
|
func file_api_pb_common_proto_init() {
|
||||||
|
if File_api_pb_common_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_api_pb_common_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*Metadata); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_common_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*Empty); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_common_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*EmptyResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_common_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*IP); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_common_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*IPPort); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_common_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*IPPrefix); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_common_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*LogsRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_pb_common_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*LogEntry); 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_common_proto_rawDesc,
|
||||||
|
NumEnums: 1,
|
||||||
|
NumMessages: 8,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_api_pb_common_proto_goTypes,
|
||||||
|
DependencyIndexes: file_api_pb_common_proto_depIdxs,
|
||||||
|
EnumInfos: file_api_pb_common_proto_enumTypes,
|
||||||
|
MessageInfos: file_api_pb_common_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_api_pb_common_proto = out.File
|
||||||
|
file_api_pb_common_proto_rawDesc = nil
|
||||||
|
file_api_pb_common_proto_goTypes = nil
|
||||||
|
file_api_pb_common_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -2,16 +2,22 @@ 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";
|
||||||
|
|
||||||
// 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 is undefined.
|
||||||
string error = 2;
|
string error = 2;
|
||||||
// error as a gRPC Status message.
|
// error as a gRPC Status message.
|
||||||
@@ -42,3 +48,25 @@ message IPPrefix {
|
|||||||
IP ip = 1;
|
IP ip = 1;
|
||||||
uint32 bits = 2;
|
uint32 bits = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LogsRequest {
|
||||||
|
string id = 1;
|
||||||
|
// Options for logs retrieval.
|
||||||
|
bool follow = 2;
|
||||||
|
int32 tail = 3; // -1 means all
|
||||||
|
string since = 4; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
string until = 5; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
}
|
||||||
|
|
||||||
|
message LogEntry {
|
||||||
|
enum StreamType {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
STDOUT = 1;
|
||||||
|
STDERR = 2;
|
||||||
|
HEARTBEAT = 3;
|
||||||
|
}
|
||||||
|
StreamType stream = 1;
|
||||||
|
google.protobuf.Timestamp timestamp = 2;
|
||||||
|
// Log line content. Empty for heartbeat entries.
|
||||||
|
bytes message = 3;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -2,11 +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 "google/protobuf/timestamp.proto";
|
import "api/pb/common.proto";
|
||||||
import "internal/machine/api/pb/common.proto";
|
|
||||||
|
|
||||||
service Docker {
|
service Docker {
|
||||||
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
|
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
|
||||||
@@ -17,7 +16,7 @@ service Docker {
|
|||||||
rpc RemoveContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
rpc RemoveContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
rpc ExecContainer(stream ExecContainerRequest) returns (stream ExecContainerResponse);
|
rpc ExecContainer(stream ExecContainerRequest) returns (stream ExecContainerResponse);
|
||||||
rpc ContainerLogs(ContainerLogsRequest) returns (stream ContainerLogEntry);
|
rpc ContainerLogs(LogsRequest) returns (stream LogEntry);
|
||||||
|
|
||||||
rpc PullImage(PullImageRequest) returns (stream JSONMessage);
|
rpc PullImage(PullImageRequest) returns (stream JSONMessage);
|
||||||
rpc InspectImage(InspectImageRequest) returns (InspectImageResponse);
|
rpc InspectImage(InspectImageRequest) returns (InspectImageResponse);
|
||||||
@@ -132,28 +131,6 @@ message ExecContainerResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ContainerLogsRequest {
|
|
||||||
string container_id = 1;
|
|
||||||
// Options for logs retrieval.
|
|
||||||
bool follow = 2;
|
|
||||||
int32 tail = 3; // -1 means all
|
|
||||||
string since = 4; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
|
||||||
string until = 5; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContainerLogEntry {
|
|
||||||
enum StreamType {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
STDOUT = 1;
|
|
||||||
STDERR = 2;
|
|
||||||
HEARTBEAT = 3;
|
|
||||||
}
|
|
||||||
StreamType stream = 1;
|
|
||||||
google.protobuf.Timestamp timestamp = 2;
|
|
||||||
// Log line content. Empty for heartbeat entries.
|
|
||||||
bytes message = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PullImageRequest {
|
message PullImageRequest {
|
||||||
string image = 1;
|
string image = 1;
|
||||||
// JSON serialised image.PullOptions.
|
// JSON serialised image.PullOptions.
|
||||||
@@ -251,6 +228,15 @@ message CreateServiceContainerRequest {
|
|||||||
// JSON serialised api.ServiceSpec.
|
// JSON serialised api.ServiceSpec.
|
||||||
bytes service_spec = 2;
|
bytes service_spec = 2;
|
||||||
string container_name = 3;
|
string container_name = 3;
|
||||||
|
|
||||||
|
// ContainerType specifies the purpose of the container being created.
|
||||||
|
enum ContainerType {
|
||||||
|
// SERVICE is the default type for long-running service containers.
|
||||||
|
SERVICE = 0;
|
||||||
|
// PRE_DEPLOY is a one-shot container for a pre-deploy hook to run before service deployment.
|
||||||
|
PRE_DEPLOY = 1;
|
||||||
|
}
|
||||||
|
ContainerType container_type = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ServiceContainer {
|
message ServiceContainer {
|
||||||
@@ -275,4 +261,6 @@ message ListServiceContainersResponse {
|
|||||||
message MachineServiceContainers {
|
message MachineServiceContainers {
|
||||||
Metadata metadata = 1;
|
Metadata metadata = 1;
|
||||||
repeated ServiceContainer containers = 2;
|
repeated ServiceContainer containers = 2;
|
||||||
|
// One-shot containers for deployment hooks (e.g. pre-deploy).
|
||||||
|
repeated ServiceContainer hook_containers = 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/docker.proto
|
// source: api/pb/docker.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ type DockerClient interface {
|
|||||||
ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error)
|
ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error)
|
||||||
RemoveContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
RemoveContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
ExecContainer(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse], error)
|
ExecContainer(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse], error)
|
||||||
ContainerLogs(ctx context.Context, in *ContainerLogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ContainerLogEntry], error)
|
ContainerLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error)
|
||||||
PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error)
|
PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error)
|
||||||
InspectImage(ctx context.Context, in *InspectImageRequest, opts ...grpc.CallOption) (*InspectImageResponse, error)
|
InspectImage(ctx context.Context, in *InspectImageRequest, opts ...grpc.CallOption) (*InspectImageResponse, error)
|
||||||
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
||||||
@@ -149,13 +149,13 @@ func (c *dockerClient) ExecContainer(ctx context.Context, opts ...grpc.CallOptio
|
|||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type Docker_ExecContainerClient = grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse]
|
type Docker_ExecContainerClient = grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse]
|
||||||
|
|
||||||
func (c *dockerClient) ContainerLogs(ctx context.Context, in *ContainerLogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ContainerLogEntry], error) {
|
func (c *dockerClient) ContainerLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &Docker_ServiceDesc.Streams[1], Docker_ContainerLogs_FullMethodName, cOpts...)
|
stream, err := c.cc.NewStream(ctx, &Docker_ServiceDesc.Streams[1], Docker_ContainerLogs_FullMethodName, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
x := &grpc.GenericClientStream[ContainerLogsRequest, ContainerLogEntry]{ClientStream: stream}
|
x := &grpc.GenericClientStream[LogsRequest, LogEntry]{ClientStream: stream}
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ func (c *dockerClient) ContainerLogs(ctx context.Context, in *ContainerLogsReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type Docker_ContainerLogsClient = grpc.ServerStreamingClient[ContainerLogEntry]
|
type Docker_ContainerLogsClient = grpc.ServerStreamingClient[LogEntry]
|
||||||
|
|
||||||
func (c *dockerClient) PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error) {
|
func (c *dockerClient) PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
@@ -298,7 +298,7 @@ type DockerServer interface {
|
|||||||
ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error)
|
ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error)
|
||||||
RemoveContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
RemoveContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
||||||
ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error
|
ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error
|
||||||
ContainerLogs(*ContainerLogsRequest, grpc.ServerStreamingServer[ContainerLogEntry]) error
|
ContainerLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error
|
||||||
PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error
|
PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error
|
||||||
InspectImage(context.Context, *InspectImageRequest) (*InspectImageResponse, error)
|
InspectImage(context.Context, *InspectImageRequest) (*InspectImageResponse, error)
|
||||||
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
||||||
@@ -343,7 +343,7 @@ func (UnimplementedDockerServer) RemoveContainer(context.Context, *RemoveContain
|
|||||||
func (UnimplementedDockerServer) ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error {
|
func (UnimplementedDockerServer) ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method ExecContainer not implemented")
|
return status.Errorf(codes.Unimplemented, "method ExecContainer not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedDockerServer) ContainerLogs(*ContainerLogsRequest, grpc.ServerStreamingServer[ContainerLogEntry]) error {
|
func (UnimplementedDockerServer) ContainerLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method ContainerLogs not implemented")
|
return status.Errorf(codes.Unimplemented, "method ContainerLogs not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedDockerServer) PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error {
|
func (UnimplementedDockerServer) PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error {
|
||||||
@@ -516,15 +516,15 @@ func _Docker_ExecContainer_Handler(srv interface{}, stream grpc.ServerStream) er
|
|||||||
type Docker_ExecContainerServer = grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]
|
type Docker_ExecContainerServer = grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]
|
||||||
|
|
||||||
func _Docker_ContainerLogs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _Docker_ContainerLogs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(ContainerLogsRequest)
|
m := new(LogsRequest)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return srv.(DockerServer).ContainerLogs(m, &grpc.GenericServerStream[ContainerLogsRequest, ContainerLogEntry]{ServerStream: stream})
|
return srv.(DockerServer).ContainerLogs(m, &grpc.GenericServerStream[LogsRequest, LogEntry]{ServerStream: stream})
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type Docker_ContainerLogsServer = grpc.ServerStreamingServer[ContainerLogEntry]
|
type Docker_ContainerLogsServer = grpc.ServerStreamingServer[LogEntry]
|
||||||
|
|
||||||
func _Docker_PullImage_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _Docker_PullImage_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(PullImageRequest)
|
m := new(PullImageRequest)
|
||||||
@@ -807,5 +807,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,9 +26,12 @@ 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"
|
||||||
|
Machine_MachineLogs_FullMethodName = "/api.Machine/MachineLogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MachineClient is the client API for Machine service.
|
// MachineClient is the client API for Machine service.
|
||||||
@@ -44,11 +47,30 @@ 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.
|
||||||
Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error)
|
InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error)
|
||||||
|
MachineLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type machineClient struct {
|
type machineClient struct {
|
||||||
@@ -119,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)
|
||||||
@@ -149,6 +191,25 @@ func (c *machineClient) InspectService(ctx context.Context, in *InspectServiceRe
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *machineClient) MachineLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &Machine_ServiceDesc.Streams[0], Machine_MachineLogs_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[LogsRequest, LogEntry]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type Machine_MachineLogsClient = grpc.ServerStreamingClient[LogEntry]
|
||||||
|
|
||||||
// MachineServer is the server API for Machine service.
|
// MachineServer is the server API for Machine service.
|
||||||
// All implementations must embed UnimplementedMachineServer
|
// All implementations must embed UnimplementedMachineServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -162,11 +223,30 @@ 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.
|
||||||
Reset(context.Context, *ResetRequest) (*emptypb.Empty, error)
|
Reset(context.Context, *ResetRequest) (*emptypb.Empty, error)
|
||||||
InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error)
|
InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error)
|
||||||
|
MachineLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error
|
||||||
mustEmbedUnimplementedMachineServer()
|
mustEmbedUnimplementedMachineServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,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")
|
||||||
}
|
}
|
||||||
@@ -204,6 +290,9 @@ func (UnimplementedMachineServer) Reset(context.Context, *ResetRequest) (*emptyp
|
|||||||
func (UnimplementedMachineServer) InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error) {
|
func (UnimplementedMachineServer) InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method InspectService not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method InspectService not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedMachineServer) MachineLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method MachineLogs not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedMachineServer) mustEmbedUnimplementedMachineServer() {}
|
func (UnimplementedMachineServer) mustEmbedUnimplementedMachineServer() {}
|
||||||
func (UnimplementedMachineServer) testEmbeddedByValue() {}
|
func (UnimplementedMachineServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@@ -333,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 {
|
||||||
@@ -387,6 +512,17 @@ func _Machine_InspectService_Handler(srv interface{}, ctx context.Context, dec f
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Machine_MachineLogs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(LogsRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(MachineServer).MachineLogs(m, &grpc.GenericServerStream[LogsRequest, LogEntry]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type Machine_MachineLogsServer = grpc.ServerStreamingServer[LogEntry]
|
||||||
|
|
||||||
// Machine_ServiceDesc is the grpc.ServiceDesc for Machine service.
|
// Machine_ServiceDesc is the grpc.ServiceDesc for Machine service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -418,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,
|
||||||
@@ -431,6 +575,12 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _Machine_InspectService_Handler,
|
Handler: _Machine_InspectService_Handler,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{
|
||||||
Metadata: "internal/machine/api/pb/machine.proto",
|
{
|
||||||
|
StreamName: "MachineLogs",
|
||||||
|
Handler: _Machine_MachineLogs_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Metadata: "api/pb/machine.proto",
|
||||||
}
|
}
|
||||||
Vendored
File renamed without changes.
@@ -0,0 +1,4 @@
|
|||||||
|
_ _ ___
|
||||||
|
| | | |/ __|
|
||||||
|
| |_| | (__
|
||||||
|
\__,_|\___|
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
composecli "github.com/compose-spec/compose-go/v2/cli"
|
composecli "github.com/compose-spec/compose-go/v2/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/pkg/client/compose"
|
"github.com/psviderski/uncloud/pkg/client/compose"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -50,6 +51,9 @@ to cluster machines or --push-registry to upload them to external registries.`,
|
|||||||
return runBuild(cmd.Context(), uncli, opts)
|
return runBuild(cmd.Context(), uncli, opts)
|
||||||
},
|
},
|
||||||
GroupID: "service",
|
GroupID: "service",
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
return completion.ComposeServices(cmd.Context(), args, toComplete, opts.files, opts.profiles)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringArrayVar(&opts.BuildArgs, "build-arg", nil,
|
cmd.Flags().StringArrayVar(&opts.BuildArgs, "build-arg", nil,
|
||||||
@@ -76,6 +80,8 @@ to cluster machines or --push-registry to upload them to external registries.`,
|
|||||||
cmd.Flags().BoolVar(&opts.PushRegistry, "push-registry", false,
|
cmd.Flags().BoolVar(&opts.PushRegistry, "push-registry", false,
|
||||||
"Upload the built images to external registries (e.g., Docker Hub) after building.")
|
"Upload the built images to external registries (e.g., Docker Hub) after building.")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package cert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listOptions struct {
|
||||||
|
machine string
|
||||||
|
output string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListCommand() *cobra.Command {
|
||||||
|
opts := listOptions{}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Aliases: []string{"list"},
|
||||||
|
Short: "List certificates in cluster storage for Caddy.",
|
||||||
|
Long: `List certificates stored in the Uncloud cluster storage for Caddy.
|
||||||
|
|
||||||
|
Caddy must use the Uncloud storage module (https://github.com/unlabs-dev/caddy-uncloud)
|
||||||
|
configured with 'storage uncloud' in the global options for its certificates to appear here.
|
||||||
|
|
||||||
|
This inventory does not check whether Caddy currently serves or trusts a certificate.`,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return list(cmd.Context(), uncli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||||
|
"Name or ID of the machine to read certificate storage from. (default is connected machine)")
|
||||||
|
cmd.Flags().StringVarP(&opts.output, "output", "o", "",
|
||||||
|
"Output format: 'json' or empty for a human-readable table.")
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
||||||
|
if opts.output != "" && opts.output != "json" {
|
||||||
|
return fmt.Errorf("unsupported output format '%s' (supported: json)", opts.output)
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterClient, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer clusterClient.Close()
|
||||||
|
|
||||||
|
certs, listErr := clusterClient.Caddy.ListCertificates(ctx,
|
||||||
|
client.CaddyListCertificatesOptions{Machine: opts.machine})
|
||||||
|
if len(certs) > 0 || listErr == nil {
|
||||||
|
if err = printCertificates(os.Stdout, certs, opts.output); err != nil {
|
||||||
|
return fmt.Errorf("print Caddy certificates: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if listErr != nil {
|
||||||
|
if len(certs) > 0 {
|
||||||
|
return fmt.Errorf("certificate list is partial: %w", listErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("list Caddy certificates: %w", listErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printCertificates(out io.Writer, certs []api.IssuedCertificate, output string) error {
|
||||||
|
type certificate struct {
|
||||||
|
api.IssuedCertificate
|
||||||
|
Fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]certificate, 0, len(certs))
|
||||||
|
for _, cert := range certs {
|
||||||
|
fingerprint := cert.Fingerprint()
|
||||||
|
items = append(items, certificate{
|
||||||
|
IssuedCertificate: cert,
|
||||||
|
Fingerprint: hex.EncodeToString(fingerprint[:]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
slices.SortFunc(items, func(a, b certificate) int {
|
||||||
|
if cmp := strings.Compare(a.SAN, b.SAN); cmp != 0 {
|
||||||
|
return cmp
|
||||||
|
}
|
||||||
|
if cmp := a.Chain[0].NotAfter.Compare(b.Chain[0].NotAfter); cmp != 0 {
|
||||||
|
return cmp
|
||||||
|
}
|
||||||
|
return strings.Compare(a.Fingerprint, b.Fingerprint)
|
||||||
|
})
|
||||||
|
|
||||||
|
if output == "json" {
|
||||||
|
encoder := json.NewEncoder(out)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
if err := encoder.Encode(items); err != nil {
|
||||||
|
return fmt.Errorf("marshal certificates: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
_, err := fmt.Fprintln(out, "No Caddy certificates found in cluster storage.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tui.NewTable()
|
||||||
|
t.Headers("ID", "NAME", "ISSUER", "EXPIRES")
|
||||||
|
for _, item := range items {
|
||||||
|
t.Row(
|
||||||
|
item.Fingerprint[:12],
|
||||||
|
item.SAN,
|
||||||
|
formatIssuer(item.IssuerData),
|
||||||
|
formatExpiry(item.Chain[0].NotAfter),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_, err := lipgloss.Fprintln(out, t)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatIssuer(data api.CertificateIssuerData) string {
|
||||||
|
if data.ACME == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data.ACME.CA {
|
||||||
|
case certmagic.LetsEncryptProductionCA:
|
||||||
|
return "Let's Encrypt"
|
||||||
|
case certmagic.LetsEncryptStagingCA:
|
||||||
|
return "Let's Encrypt (staging)"
|
||||||
|
case certmagic.ZeroSSLProductionCA:
|
||||||
|
return "ZeroSSL"
|
||||||
|
case certmagic.GoogleTrustProductionCA:
|
||||||
|
return "Google Trust Services"
|
||||||
|
case certmagic.GoogleTrustStagingCA:
|
||||||
|
return "Google Trust Services (staging)"
|
||||||
|
default:
|
||||||
|
return data.ACME.CA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatExpiry(expires time.Time) string {
|
||||||
|
delta := expires.Sub(time.Now())
|
||||||
|
if delta <= 0 {
|
||||||
|
return fmt.Sprintf("%s (expired %s ago)", expires.UTC().Format(time.DateOnly),
|
||||||
|
strings.ToLower(units.HumanDuration(-delta)))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (%s)", expires.UTC().Format(time.DateOnly),
|
||||||
|
strings.ToLower(units.HumanDuration(delta)))
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ import (
|
|||||||
|
|
||||||
"github.com/alecthomas/chroma/v2/quick"
|
"github.com/alecthomas/chroma/v2/quick"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +36,8 @@ func NewConfigCommand() *cobra.Command {
|
|||||||
cmd.Flags().BoolVar(&opts.noColor, "no-color", false,
|
cmd.Flags().BoolVar(&opts.noColor, "no-color", false,
|
||||||
"Disable syntax highlighting for the output.")
|
"Disable syntax highlighting for the output.")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,15 +48,7 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -66,5 +63,10 @@ func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.LastReconciliationError != "" {
|
||||||
|
tui.PrintWarning(fmt.Sprintf("last Caddy config load failed: %s\nShowing the last saved Caddyfile.",
|
||||||
|
config.LastReconciliationError))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -12,9 +12,10 @@ import (
|
|||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
|
"github.com/psviderski/uncloud/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -48,6 +49,8 @@ func NewDeployCommand() *cobra.Command {
|
|||||||
"Machine names or IDs to deploy to. Can be specified multiple times or as a comma-separated "+
|
"Machine names or IDs to deploy to. Can be specified multiple times or as a comma-separated "+
|
||||||
"list. (default is all machines)")
|
"list. (default is all machines)")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +91,12 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
if len(currentImages) > 1 {
|
if len(currentImages) > 1 {
|
||||||
formattedImages := make([]string, len(currentImages))
|
formattedImages := make([]string, len(currentImages))
|
||||||
for i, img := range currentImages {
|
for i, img := range currentImages {
|
||||||
formattedImages[i] = tui.FormatImage(img, lipgloss.NewStyle())
|
formattedImages[i] = tui.FormatImage(img, tui.NoStyle)
|
||||||
}
|
}
|
||||||
fmt.Println(tui.Faint.Render("current images (multiple versions detected): ") +
|
fmt.Println(tui.Faint.Render("current images (multiple versions detected): ") +
|
||||||
strings.Join(formattedImages, tui.Faint.Render(", ")))
|
strings.Join(formattedImages, tui.Faint.Render(", ")))
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(tui.Faint.Render("current image: ") + tui.FormatImage(currentImages[0], lipgloss.NewStyle()))
|
fmt.Println(tui.Faint.Render("current image: ") + tui.FormatImage(currentImages[0], tui.NoStyle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +110,9 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
placement := api.Placement{
|
placement := api.Placement{
|
||||||
Machines: cli.ExpandCommaSeparatedValues(opts.machines),
|
Machines: cli.ExpandCommaSeparatedValues(opts.machines),
|
||||||
}
|
}
|
||||||
d, err := clusterClient.NewCaddyDeployment(opts.image, caddyfile, placement)
|
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{
|
||||||
|
Image: opts.image, Config: caddyfile, Placement: placement,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create caddy deployment: %w", err)
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
}
|
}
|
||||||
@@ -172,8 +177,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
return fmt.Errorf("confirm deployment: %w", err)
|
return fmt.Errorf("confirm deployment: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
fmt.Println("Cancelled. No changes were made.")
|
return cli.Cancelled("Caddy deploy cancelled. No changes were made.")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
@@ -200,6 +204,10 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("get cluster domain: %w", err)
|
return fmt.Errorf("get cluster domain: %w", err)
|
||||||
}
|
}
|
||||||
|
if !domain.Reserved {
|
||||||
|
fmt.Printf("Skipping DNS records update as cluster domain '%s' is managed externally.\n", domain.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Updating cluster domain records in Uncloud DNS to point to machines running caddy service...")
|
fmt.Println("Updating cluster domain records in Uncloud DNS to point to machines running caddy service...")
|
||||||
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
||||||
@@ -214,7 +222,7 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
if errors.Is(err, client.ErrNoReachableMachines) {
|
if errors.Is(err, client.ErrNoReachableMachines) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
||||||
"machines running caddy containers.\n", domain)
|
"machines running caddy containers.\n", domain.Name)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Possible solutions:")
|
fmt.Println("Possible solutions:")
|
||||||
fmt.Println("- Ensure your machines have public IP addresses")
|
fmt.Println("- Ensure your machines have public IP addresses")
|
||||||
@@ -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)
|
||||||
@@ -3,11 +3,11 @@ package context
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
|
"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/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,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)
|
||||||
@@ -38,8 +39,8 @@ func list(uncli *cli.CLI) error {
|
|||||||
contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
||||||
currentContext := uncli.Config.CurrentContext
|
currentContext := uncli.Config.CurrentContext
|
||||||
|
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
t := tui.NewTable()
|
||||||
fmt.Fprintln(tw, "NAME\tCURRENT\tCONNECTIONS")
|
t.Headers("NAME", "CURRENT", "CONNECTIONS")
|
||||||
|
|
||||||
for _, name := range contextNames {
|
for _, name := range contextNames {
|
||||||
current := ""
|
current := ""
|
||||||
@@ -47,8 +48,9 @@ func list(uncli *cli.CLI) error {
|
|||||||
current = "✓"
|
current = "✓"
|
||||||
}
|
}
|
||||||
connCount := len(uncli.Config.Contexts[name].Connections)
|
connCount := len(uncli.Config.Contexts[name].Connections)
|
||||||
fmt.Fprintf(tw, "%s\t%s\t%d\n", name, current, connCount)
|
t.Row(name, current, fmt.Sprintf("%d", connCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
return tw.Flush()
|
lipgloss.Println(t)
|
||||||
|
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)
|
||||||
@@ -20,6 +21,7 @@ func NewRootCommand() *cobra.Command {
|
|||||||
NewListCommand(),
|
NewListCommand(),
|
||||||
NewUseCommand(),
|
NewUseCommand(),
|
||||||
NewConnectionCommand(),
|
NewConnectionCommand(),
|
||||||
|
NewShowCommand(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewShowCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "show",
|
||||||
|
Short: "Show current cluster context.",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return show(uncli)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func show(uncli *cli.CLI) error {
|
||||||
|
// discard errors, only show the current context, otherwise nothing
|
||||||
|
if uncli.Config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(uncli.Config.Contexts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Println(uncli.Config.CurrentContext)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ 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/tui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,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)
|
||||||
|
|
||||||
@@ -29,6 +32,13 @@ func NewUseCommand() *cobra.Command {
|
|||||||
|
|
||||||
return selectContext(uncli)
|
return selectContext(uncli)
|
||||||
},
|
},
|
||||||
|
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.Contexts(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
@@ -41,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))
|
||||||
|
|
||||||
@@ -9,11 +9,16 @@ import (
|
|||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
composecli "github.com/compose-spec/compose-go/v2/cli"
|
composecli "github.com/compose-spec/compose-go/v2/cli"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"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"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/logs"
|
||||||
"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/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/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,6 +49,9 @@ func NewDeployCommand() *cobra.Command {
|
|||||||
return runDeploy(cmd.Context(), uncli, opts)
|
return runDeploy(cmd.Context(), uncli, opts)
|
||||||
},
|
},
|
||||||
GroupID: "service",
|
GroupID: "service",
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
return completion.ComposeServices(cmd.Context(), args, toComplete, opts.files, opts.profiles)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringArrayVar(&opts.BuildServicesOptions.BuildArgs, "build-arg", nil,
|
cmd.Flags().StringArrayVar(&opts.BuildServicesOptions.BuildArgs, "build-arg", nil,
|
||||||
@@ -155,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,
|
||||||
@@ -195,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")
|
||||||
}
|
}
|
||||||
@@ -214,8 +230,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
return fmt.Errorf("confirm deployment: %w", err)
|
return fmt.Errorf("confirm deployment: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
fmt.Println("Cancelled. No changes were made.")
|
return cli.Cancelled("Deploy cancelled. No changes were made.")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,10 +238,76 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
if deployTarget != "" {
|
if deployTarget != "" {
|
||||||
title += " to " + tui.NameStyle.Render(deployTarget)
|
title += " to " + tui.NameStyle.Render(deployTarget)
|
||||||
}
|
}
|
||||||
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
if err := plan.Execute(ctx, clusterClient); err != nil {
|
if err := plan.Execute(ctx, clusterClient); err != nil {
|
||||||
return fmt.Errorf("deploy services: %w", err)
|
return fmt.Errorf("deploy services: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, uncli.ProgressOut(), title)
|
}, uncli.ProgressOut(), title)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
tail := failedContainerLogsTail()
|
||||||
|
if hookErr, ok := errors.AsType[*operation.PreDeployHookError](err); ok {
|
||||||
|
printFailedContainerLogs(ctx, clusterClient,
|
||||||
|
hookErr.ServiceName, hookErr.ContainerID, hookErr.MachineName, tail,
|
||||||
|
fmt.Sprintf("Last %d log lines from failed pre-deploy hook:", tail))
|
||||||
|
fmt.Println()
|
||||||
|
} else if startErr, ok := errors.AsType[*operation.ContainerHealthError](err); ok {
|
||||||
|
printFailedContainerLogs(ctx, clusterClient,
|
||||||
|
startErr.ServiceName, startErr.ContainerID, startErr.MachineName, tail,
|
||||||
|
fmt.Sprintf("Last %d log lines from failed container:", tail))
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFailedContainerLogs fetches the last tail log lines from a container that failed during deployment and prints
|
||||||
|
// them using the standard log formatter under the provided header.
|
||||||
|
func printFailedContainerLogs(
|
||||||
|
ctx context.Context, cli *client.Client, serviceName, containerID, machineName string, tail int, header string,
|
||||||
|
) {
|
||||||
|
_, ch, err := cli.ServiceLogs(ctx, serviceName, api.ServiceLogsOptions{
|
||||||
|
Containers: []string{containerID},
|
||||||
|
Machines: []string{machineName},
|
||||||
|
Tail: tail,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
shortCtrID := stringid.TruncateID(containerID)
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fetch container logs '%s/%s': %v\n", serviceName, shortCtrID, err)
|
||||||
|
fmt.Fprintf(os.Stderr, "You can try manually with: uc logs %s/%s\n", serviceName, shortCtrID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(tui.BoldRed.Render(header))
|
||||||
|
|
||||||
|
logsEmpty := true
|
||||||
|
formatter := logs.NewFormatter([]string{machineName}, []string{serviceName}, false)
|
||||||
|
for entry := range ch {
|
||||||
|
logsEmpty = false
|
||||||
|
formatter.PrintEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
if logsEmpty {
|
||||||
|
fmt.Println("<no logs available>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultFailedContainerLogsTail is the default number of recent log lines to print from a failed container to give
|
||||||
|
// the user immediate context without requiring a follow-up 'uc logs' invocation.
|
||||||
|
// Overridable via UNCLOUD_FAILED_CONTAINER_LOGS_TAIL.
|
||||||
|
const defaultFailedContainerLogsTail = 10
|
||||||
|
|
||||||
|
// failedContainerLogsTail returns the number of log lines to fetch from a failed container, honouring the
|
||||||
|
// UNCLOUD_FAILED_CONTAINER_LOGS_TAIL environment variable override when set and valid.
|
||||||
|
func failedContainerLogsTail() int {
|
||||||
|
if v := os.Getenv("UNCLOUD_FAILED_CONTAINER_LOGS_TAIL"); v != "" {
|
||||||
|
if tail, err := logs.Tail(v); err == nil && (tail == -1 || tail > 0) {
|
||||||
|
return tail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultFailedContainerLogsTail
|
||||||
}
|
}
|
||||||
@@ -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,10 @@ import (
|
|||||||
|
|
||||||
const docsDir = "website/docs/9-cli-reference"
|
const docsDir = "website/docs/9-cli-reference"
|
||||||
|
|
||||||
|
type docOptions struct {
|
||||||
|
manual bool
|
||||||
|
}
|
||||||
|
|
||||||
type cmdWrapper struct {
|
type cmdWrapper struct {
|
||||||
cmd *cobra.Command
|
cmd *cobra.Command
|
||||||
}
|
}
|
||||||
@@ -20,6 +24,7 @@ type cmdWrapper struct {
|
|||||||
// NewDocsCommand creates a new hidden command to generate CLI reference docs.
|
// NewDocsCommand creates a new hidden command to generate CLI reference docs.
|
||||||
func NewDocsCommand() *cobra.Command {
|
func NewDocsCommand() *cobra.Command {
|
||||||
wrapper := &cmdWrapper{}
|
wrapper := &cmdWrapper{}
|
||||||
|
opts := docOptions{}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "docs",
|
Use: "docs",
|
||||||
Short: "Generate Uncloud CLI reference docs",
|
Short: "Generate Uncloud CLI reference docs",
|
||||||
@@ -29,6 +34,28 @@ func NewDocsCommand() *cobra.Command {
|
|||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
ValidArgsFunction: cobra.NoFileCompletions,
|
ValidArgsFunction: cobra.NoFileCompletions,
|
||||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
if opts.manual {
|
||||||
|
header := &doc.GenManHeader{
|
||||||
|
Title: "Uncloud",
|
||||||
|
Section: "1",
|
||||||
|
Source: "Uncloud https://uncloud.run",
|
||||||
|
}
|
||||||
|
if err := doc.GenManTree(cmd.Root(), header, "."); err != nil {
|
||||||
|
return fmt.Errorf("generate CLI manual pages: %w", err)
|
||||||
|
}
|
||||||
|
completionFiles, err := filepath.Glob("uc-completion*.1")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("list generated manual pages: %w", err)
|
||||||
|
}
|
||||||
|
for _, f := range completionFiles {
|
||||||
|
if err = os.Remove(f); err != nil {
|
||||||
|
return fmt.Errorf("remove '%s': %w", f, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Remove existing markdown files.
|
// Remove existing markdown files.
|
||||||
mdFiles, err := filepath.Glob(filepath.Join(docsDir, "*.md"))
|
mdFiles, err := filepath.Glob(filepath.Join(docsDir, "*.md"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,6 +99,8 @@ func NewDocsCommand() *cobra.Command {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cmd.Flags().BoolVar(&opts.manual, "manual", false,
|
||||||
|
"Generate Uncloud manual pages in the current directory.")
|
||||||
|
|
||||||
wrapper.cmd = cmd
|
wrapper.cmd = cmd
|
||||||
return cmd
|
return cmd
|
||||||
@@ -10,13 +10,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"charm.land/lipgloss/v2/table"
|
|
||||||
"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/go-units"
|
"github.com/docker/go-units"
|
||||||
|
|
||||||
"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/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -62,6 +63,8 @@ func NewListCommand() *cobra.Command {
|
|||||||
"Filter images by machine name or ID. Can be specified multiple times or as a comma-separated list. "+
|
"Filter images by machine name or ID. Can be specified multiple times or as a comma-separated list. "+
|
||||||
"(default is include all machines)")
|
"(default is include all machines)")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,19 +88,6 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
}
|
}
|
||||||
defer clusterClient.Close()
|
defer clusterClient.Close()
|
||||||
|
|
||||||
// Get all machines to create ID to name mapping.
|
|
||||||
allMachines, err := clusterClient.ListMachines(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("list machines: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
machineIDToName := make(map[string]string)
|
|
||||||
for _, machineMember := range allMachines {
|
|
||||||
if machineMember.Machine != nil && machineMember.Machine.Id != "" && machineMember.Machine.Name != "" {
|
|
||||||
machineIDToName[machineMember.Machine.Id] = machineMember.Machine.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
machines := cli.ExpandCommaSeparatedValues(opts.machines)
|
machines := cli.ExpandCommaSeparatedValues(opts.machines)
|
||||||
|
|
||||||
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{
|
clusterImages, err := clusterClient.ListImages(ctx, api.ImageFilter{
|
||||||
@@ -112,12 +102,15 @@ 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.Error(); 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"
|
||||||
@@ -186,7 +179,7 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Print the images in a table format.
|
// Print the images in a table format.
|
||||||
fmt.Println(formatImageTable(rows))
|
lipgloss.Println(formatImageTable(rows))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -260,22 +253,15 @@ func formatImageTable(rows []imageRow) string {
|
|||||||
columns[5].hide = true
|
columns[5].hide = true
|
||||||
}
|
}
|
||||||
|
|
||||||
t := table.New().
|
// Hide the "STORE" column if all machines use the containerd store.
|
||||||
// Remove the default border.
|
hasNonContainerd := slices.ContainsFunc(rows, func(r imageRow) bool {
|
||||||
Border(lipgloss.Border{}).
|
return r.store != "containerd"
|
||||||
BorderTop(false).
|
|
||||||
BorderBottom(false).
|
|
||||||
BorderLeft(false).
|
|
||||||
BorderRight(false).
|
|
||||||
BorderHeader(false).
|
|
||||||
BorderColumn(false).
|
|
||||||
StyleFunc(func(row, col int) lipgloss.Style {
|
|
||||||
if row == table.HeaderRow {
|
|
||||||
return lipgloss.NewStyle().Bold(true).PaddingRight(3)
|
|
||||||
}
|
|
||||||
// Regular style for data rows with padding.
|
|
||||||
return lipgloss.NewStyle().PaddingRight(3)
|
|
||||||
})
|
})
|
||||||
|
if !hasNonContainerd {
|
||||||
|
columns[6].hide = true
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tui.NewTable()
|
||||||
|
|
||||||
var headers []string
|
var headers []string
|
||||||
for _, col := range columns {
|
for _, col := range columns {
|
||||||
@@ -288,7 +274,7 @@ func formatImageTable(rows []imageRow) string {
|
|||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
values := []string{
|
values := []string{
|
||||||
row.id,
|
row.id,
|
||||||
row.name,
|
tui.FormatImage(row.name, tui.NoStyle),
|
||||||
row.platforms,
|
row.platforms,
|
||||||
row.createdHuman,
|
row.createdHuman,
|
||||||
row.size,
|
row.size,
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/containerd/platforms"
|
"github.com/containerd/platforms"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -54,6 +55,8 @@ The image is uploaded to all cluster machines (default) or the specified machine
|
|||||||
"Local Docker must be configured to use containerd image store to support multi-platform images.",
|
"Local Docker must be configured to use containerd image store to support multi-platform images.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
File renamed without changes.
@@ -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,18 +40,25 @@ 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.
|
||||||
|
|
||||||
Connection methods:
|
By default, it installs Docker and the Uncloud daemon on the machine over SSH unless --no-install
|
||||||
ssh://user@host - Use built-in SSH library (default, no prefix required)
|
is specified, which assumes they are already installed and running.
|
||||||
ssh+cli://user@host - Use system SSH command (supports ProxyJump, SSH config)`,
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
|
||||||
|
|
||||||
|
Connection methods:
|
||||||
|
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||||
|
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||||
|
cli.BindEnvToFlag(cmd, "version", "UNCLOUD_DAEMON_VERSION")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
// Determine if SSH CLI needs to be used and strip scheme
|
// Determine connection mode and strip scheme.
|
||||||
destination := args[0]
|
destination := args[0]
|
||||||
useSSHCLI := strings.HasPrefix(destination, "ssh+cli://")
|
useSSHGo := strings.HasPrefix(destination, "ssh+go://")
|
||||||
|
destination = strings.TrimPrefix(destination, "ssh+go://")
|
||||||
destination = strings.TrimPrefix(destination, "ssh+cli://")
|
destination = strings.TrimPrefix(destination, "ssh+cli://")
|
||||||
destination = strings.TrimPrefix(destination, "ssh://")
|
destination = strings.TrimPrefix(destination, "ssh://")
|
||||||
|
|
||||||
@@ -63,20 +71,21 @@ Connection methods:
|
|||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
KeyPath: opts.sshKey,
|
KeyPath: opts.sshKey,
|
||||||
UseSSHCLI: useSSHCLI,
|
UseSSHGo: useSSHGo,
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
@@ -91,15 +100,24 @@ 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 in format: IP, IP:PORT, IPv6, or [IPv6]:PORT. "+
|
"WireGuard endpoint address that other machines in the cluster should use to establish "+
|
||||||
"Default port %d is used if omitted.\n", network.WireGuardPort)+
|
"WireGuard connections\n"+
|
||||||
"Other machines in the cluster will use this endpoint to establish a WireGuard connection to this machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
|
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is the value of --wg-port if omitted.\n"+
|
||||||
"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"+
|
||||||
"If not specified, the machine's routable IPs and public IP are auto-detected and used as endpoints.",
|
"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"+
|
||||||
@@ -123,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)
|
||||||
}
|
}
|
||||||
@@ -152,19 +179,9 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the cluster to be initialised on the machine to be able to deploy the Caddy service.
|
// 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...").
|
|
||||||
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)
|
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)
|
||||||
}
|
}
|
||||||
@@ -175,7 +192,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
// Deploy a Caddy service container to the added machine. If caddy service is already deployed on other machines,
|
// Deploy a Caddy service container to the added machine. If caddy service is already deployed on other machines,
|
||||||
// use the deployed image version.
|
// use the deployed image version.
|
||||||
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
|
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
|
||||||
// issues accessing the 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 := ""
|
caddyImage := ""
|
||||||
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
||||||
@@ -202,7 +219,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Preparing Caddy deployment...")
|
fmt.Println("Preparing Caddy deployment...")
|
||||||
d, err := clusterClient.NewCaddyDeployment(caddyImage, "", api.Placement{})
|
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Image: caddyImage})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create caddy deployment: %w", err)
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
}
|
}
|
||||||
@@ -230,8 +247,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
|||||||
return fmt.Errorf("confirm deployment: %w", err)
|
return fmt.Errorf("confirm deployment: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
fmt.Println("Cancelled. No changes were made.")
|
return cli.Cancelled("Caddy deploy cancelled. The machine has been added to the cluster.")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File renamed without changes.
@@ -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,9 +45,12 @@ 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 built-in SSH library (default, no prefix required)
|
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||||
ssh+cli://user@host - Use system SSH command (supports ProxyJump, SSH config)`,
|
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||||
Example: ` # Initialise a new cluster with default settings.
|
Example: ` # Initialise a new cluster with default settings.
|
||||||
uc machine init root@<your-server-ip>
|
uc machine init root@<your-server-ip>
|
||||||
|
|
||||||
@@ -61,16 +65,20 @@ 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
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
// Determine if SSH CLI is requested and strip scheme
|
// Determine connection mode and strip scheme.
|
||||||
destination := args[0]
|
destination := args[0]
|
||||||
useSSHCLI := strings.HasPrefix(destination, "ssh+cli://")
|
useSSHGo := strings.HasPrefix(destination, "ssh+go://")
|
||||||
|
destination = strings.TrimPrefix(destination, "ssh+go://")
|
||||||
destination = strings.TrimPrefix(destination, "ssh+cli://")
|
destination = strings.TrimPrefix(destination, "ssh+cli://")
|
||||||
destination = strings.TrimPrefix(destination, "ssh://")
|
destination = strings.TrimPrefix(destination, "ssh://")
|
||||||
|
|
||||||
@@ -83,7 +91,7 @@ Connection methods:
|
|||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
KeyPath: opts.sshKey,
|
KeyPath: opts.sshKey,
|
||||||
UseSSHCLI: useSSHCLI,
|
UseSSHGo: useSSHGo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,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(),
|
||||||
@@ -115,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(
|
||||||
@@ -130,15 +138,24 @@ 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 in format: IP, IP:PORT, IPv6, or [IPv6]:PORT. "+
|
"WireGuard endpoint address that other machines in the cluster should use to establish "+
|
||||||
"Default port %d is used if omitted.\n", network.WireGuardPort)+
|
"WireGuard connections\n"+
|
||||||
"Other machines in the cluster will use this endpoint to establish a WireGuard connection to this machine.\n"+
|
"to this machine. This doesn't change the address/port WireGuard listens on the machine.\n"+
|
||||||
|
"Format: IP, IP:PORT, IPv6, or [IPv6]:PORT. Default port is the value of --wg-port if omitted.\n"+
|
||||||
"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"+
|
||||||
"If not specified, the machine's routable IPs and public IP are auto-detected and used as endpoints.",
|
"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"+
|
||||||
@@ -149,11 +166,13 @@ Connection methods:
|
|||||||
|
|
||||||
func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine, opts initOptions) error {
|
func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine, opts initOptions) error {
|
||||||
if uncli.Config == nil {
|
if uncli.Config == nil {
|
||||||
// Config is nil when connecting directly to a remote machine (--connect) without using Uncloud config.
|
// Config is nil when connecting directly to a remote machine (--connect) without using Uncloud config
|
||||||
|
// or when being logged in on a machine and using the uncloud socket directly.
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"do not use --connect when initialising a new cluster: --connect is for overriding the connection " +
|
"do not use --connect when initialising a new cluster: --connect is for overriding the connection " +
|
||||||
"to an existing cluster, but 'machine init' creates a new one and writes the new cluster context " +
|
"to an existing cluster, but 'machine init' creates a new one and writes the new cluster context " +
|
||||||
"to the Uncloud config file (--uncloud-config)")
|
"to the Uncloud config file (--uncloud-config), when logged in on a cluster machine, 'machine init' " +
|
||||||
|
"is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
netPrefix, err := netip.ParsePrefix(opts.network)
|
netPrefix, err := netip.ParsePrefix(opts.network)
|
||||||
@@ -174,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,
|
||||||
@@ -182,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)
|
||||||
}
|
}
|
||||||
@@ -227,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)
|
||||||
}
|
}
|
||||||
@@ -235,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)
|
||||||
}
|
}
|
||||||
@@ -251,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
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/logs"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLogsCommand() *cobra.Command {
|
||||||
|
var options logs.Options
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "logs [SERVICE...]",
|
||||||
|
Aliases: []string{"log"},
|
||||||
|
Short: "View system service logs.",
|
||||||
|
Long: `View logs from the specified system service(s) across all machines in the cluster.
|
||||||
|
Use -m to restrict to specific machines.
|
||||||
|
|
||||||
|
Supported services:
|
||||||
|
corrosion the Corrosion distributed state store
|
||||||
|
docker the Docker daemon
|
||||||
|
uncloud the Uncloud daemon
|
||||||
|
|
||||||
|
If no services are specified, streams logs from the uncloud service.`,
|
||||||
|
Example: ` # View recent logs for the uncloud service.
|
||||||
|
uc machine logs
|
||||||
|
uc machine logs uncloud
|
||||||
|
|
||||||
|
# Stream logs in real-time (follow mode).
|
||||||
|
uc machine logs -f uncloud
|
||||||
|
|
||||||
|
# View logs from multiple services.
|
||||||
|
uc machine logs uncloud docker corrosion
|
||||||
|
|
||||||
|
# Show last 20 lines per machine (default is 100).
|
||||||
|
uc machine logs -n 20 docker
|
||||||
|
|
||||||
|
# Show all logs without line limit.
|
||||||
|
uc machine logs -n all docker
|
||||||
|
|
||||||
|
# View logs from a specific time range.
|
||||||
|
uc machine logs --since 3h --until 1h30m docker
|
||||||
|
|
||||||
|
# View logs only from specific machines.
|
||||||
|
uc machine logs -m machine1,machine2 uncloud corrosion`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return runLogs(cmd.Context(), uncli, args, options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runLogs(ctx context.Context, uncli *cli.CLI, services []string, opts logs.Options) error {
|
||||||
|
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, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tail, err := logs.Tail(opts.Tail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
logsOpts := api.ServiceLogsOptions{
|
||||||
|
Follow: opts.Follow,
|
||||||
|
Tail: tail,
|
||||||
|
Since: opts.Since,
|
||||||
|
Until: opts.Until,
|
||||||
|
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve machine records for the formatter's column width computation.
|
||||||
|
machines, err := c.ListMachines(ctx, &api.MachineFilter{
|
||||||
|
NamesOrIDs: logsOpts.Machines,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("list machines: %w", err)
|
||||||
|
}
|
||||||
|
machineNames := make([]string, 0, len(machines))
|
||||||
|
for _, m := range machines {
|
||||||
|
machineNames = append(machineNames, m.Machine.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect one log stream per service. MachineLogs merges across machines internally.
|
||||||
|
serviceStreams := make([]<-chan api.ServiceLogEntry, 0, len(services))
|
||||||
|
for _, service := range services {
|
||||||
|
ch, err := c.MachineLogs(ctx, service, logsOpts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stream logs for system service '%s': %w", service, err)
|
||||||
|
}
|
||||||
|
serviceStreams = append(serviceStreams, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream <-chan api.ServiceLogEntry
|
||||||
|
if len(serviceStreams) == 1 {
|
||||||
|
stream = serviceStreams[0]
|
||||||
|
} else {
|
||||||
|
// Each MachineLogs stream already runs its own inner merger with stall detection,
|
||||||
|
// so the outer merger across services skips it to avoid duplicate warnings.
|
||||||
|
merger := client.NewLogMerger(serviceStreams, client.LogMergerOptions{})
|
||||||
|
stream = merger.Stream()
|
||||||
|
}
|
||||||
|
|
||||||
|
formatter := logs.NewFormatter(machineNames, services, opts.UTC)
|
||||||
|
|
||||||
|
// Print merged logs.
|
||||||
|
for entry := range stream {
|
||||||
|
formatter.PrintEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,31 +2,42 @@ package machine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
|
"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/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
||||||
@@ -38,13 +49,20 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
return fmt.Errorf("list machines: %w", err)
|
return fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the list of machines in a table format.
|
if output == "json" {
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
data, err := json.MarshalIndent(machines.ToNative(), "", " ")
|
||||||
// Print header.
|
if err != nil {
|
||||||
if _, err = fmt.Fprintln(tw, "NAME\tSTATE\tADDRESS\tPUBLIC IP\tWIREGUARD ENDPOINTS\tMACHINE ID"); err != nil {
|
return fmt.Errorf("marshal machines: %w", err)
|
||||||
return fmt.Errorf("write header: %w", err)
|
|
||||||
}
|
}
|
||||||
// Print rows.
|
fmt.Println(string(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the list of machines in a table format.
|
||||||
|
t := tui.NewTable()
|
||||||
|
t.Headers("NAME", "STATE", "ADDRESS", "PUBLIC IP", "WIREGUARD ENDPOINTS",
|
||||||
|
"OS", "KERNEL", "ARCH", "DOCKER", "VERSION")
|
||||||
|
|
||||||
for _, member := range machines {
|
for _, member := range machines {
|
||||||
m := member.Machine
|
m := member.Machine
|
||||||
subnet, _ := m.Network.Subnet.ToPrefix()
|
subnet, _ := m.Network.Subnet.ToPrefix()
|
||||||
@@ -62,14 +80,47 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
endpoints[i] = addrPort.String()
|
endpoints[i] = addrPort.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(
|
arch := "-"
|
||||||
tw, "%s\t%s\t%s\t%s\t%s\t%s\n", m.Name, capitalise(member.State.String()), subnet, publicIP,
|
if m.Arch != "" {
|
||||||
strings.Join(endpoints, ", "), member.Machine.Id,
|
arch = m.Arch
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("write row: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osName := "-"
|
||||||
|
if m.OsPrettyName != "" {
|
||||||
|
osName = m.OsPrettyName
|
||||||
}
|
}
|
||||||
return tw.Flush()
|
|
||||||
|
kernel := "-"
|
||||||
|
if m.KernelVersion != "" {
|
||||||
|
kernel = m.KernelVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
daemonVersion := "-"
|
||||||
|
if m.DaemonVersion != "" {
|
||||||
|
daemonVersion = m.DaemonVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerVersion := "-"
|
||||||
|
if m.DockerVersion != "" {
|
||||||
|
dockerVersion = m.DockerVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Row(
|
||||||
|
m.Name,
|
||||||
|
capitalise(member.State.String()),
|
||||||
|
subnet.String(),
|
||||||
|
publicIP,
|
||||||
|
strings.Join(endpoints, tui.Faint.Render(", ")),
|
||||||
|
osName,
|
||||||
|
kernel,
|
||||||
|
arch,
|
||||||
|
dockerVersion,
|
||||||
|
daemonVersion,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
lipgloss.Println(t)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// capitalise returns a string where the first character is upper case, and the rest is lower case.
|
// capitalise returns a string where the first character is upper case, and the rest is lower case.
|
||||||
File renamed without changes.
@@ -13,9 +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/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"
|
||||||
)
|
)
|
||||||
@@ -37,6 +38,13 @@ func NewRmCommand() *cobra.Command {
|
|||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return remove(cmd.Context(), uncli, args[0], opts)
|
return remove(cmd.Context(), uncli, args[0], 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.Machines(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
@@ -55,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)
|
||||||
@@ -76,9 +85,12 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
return fmt.Errorf("list machines: %w", err)
|
return fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
if len(allMachines) > 1 {
|
if len(allMachines) > 1 {
|
||||||
return errors.New("cannot remove the machine you are currently connected to. " +
|
return fmt.Errorf("cannot remove the machine you are currently connected to. "+
|
||||||
"Please connect to another machine in the cluster and try again. " +
|
"Please connect to another machine in the cluster and try again. "+
|
||||||
"Use --connect flag or update 'connections' for the cluster context in your Uncloud config")
|
"Change the default connection for the current cluster using 'uc ctx conn' or "+
|
||||||
|
"manually update 'connections' in your Uncloud config (%s). "+
|
||||||
|
"For more information on connecting to a cluster, see "+
|
||||||
|
"https://uncloud.run/docs/concepts/clusters/connecting/", uncli.Config.Path())
|
||||||
// It's ok to remove the proxy machine if it's the last one in the cluster.
|
// It's ok to remove the proxy machine if it's the last one in the cluster.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,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
|
||||||
@@ -102,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.")
|
||||||
@@ -124,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,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()
|
||||||
@@ -206,7 +219,7 @@ func formatContainerTree(containers []api.ServiceContainer) string {
|
|||||||
// Add containers as children.
|
// Add containers as children.
|
||||||
for _, ctr := range ctrs {
|
for _, ctr := range ctrs {
|
||||||
state, _ := ctr.HumanState()
|
state, _ := ctr.HumanState()
|
||||||
info := fmt.Sprintf("%s • %s • %s", ctr.Name, ctr.Config.Image, state)
|
info := fmt.Sprintf("%s • %s • %s", ctr.Name, tui.FormatImage(ctr.Config.Image, tui.NoStyle), state)
|
||||||
t.Child(info)
|
t.Child(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,8 +14,10 @@ func NewRootCommand() *cobra.Command {
|
|||||||
NewAddCommand(),
|
NewAddCommand(),
|
||||||
NewInitCommand(),
|
NewInitCommand(),
|
||||||
NewListCommand(),
|
NewListCommand(),
|
||||||
|
NewLogsCommand(),
|
||||||
NewRenameCommand(),
|
NewRenameCommand(),
|
||||||
NewRmCommand(),
|
NewRmCommand(),
|
||||||
|
NewRTTCommand(),
|
||||||
NewUpdateCommand(),
|
NewUpdateCommand(),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package machine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRTTCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "rtt",
|
||||||
|
Short: "Show round-trip times between machines.",
|
||||||
|
Long: `Show round-trip times between machines.
|
||||||
|
|
||||||
|
Round-trip time statistics are collected from the Corrosion gossip protocol
|
||||||
|
and represent the median of recent RTT samples between each pair of machines
|
||||||
|
in the cluster. The values shown include the median RTT and standard deviation
|
||||||
|
for each machine-to-machine connection.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return rtt(cmd.Context(), uncli)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtt(ctx context.Context, uncli *cli.CLI) error {
|
||||||
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
// Setup context to proxy request to all machines.
|
||||||
|
ctx = client.ProxyMachinesContext(ctx, nil)
|
||||||
|
|
||||||
|
resp, err := client.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inspect machines: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map machine IDs to names for display from the response.
|
||||||
|
machineNames := make(map[string]string)
|
||||||
|
for _, m := range resp.Machines {
|
||||||
|
// NOTE: Metadata should never be nil in practice. This is legacy fallback that will be removed.
|
||||||
|
if m.Metadata == nil {
|
||||||
|
tui.PrintWarning("metadata is missing in response from unknown server")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m.Metadata.Error != "" {
|
||||||
|
tui.PrintWarning(fmt.Sprintf("failed to inspect machine '%s': %s", m.Metadata.MachineName, m.Metadata.Error))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if m.Machine == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
machineNames[m.Machine.Id] = m.Machine.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type row struct {
|
||||||
|
machine string
|
||||||
|
peer string
|
||||||
|
median time.Duration
|
||||||
|
stdDev time.Duration
|
||||||
|
}
|
||||||
|
var rows []row
|
||||||
|
|
||||||
|
for _, m := range resp.Machines {
|
||||||
|
// Unlikely to occur, but might be a possible edge case when
|
||||||
|
// a machine is still initializing. So just to be safe.
|
||||||
|
if m.Machine == nil || m.Rtts == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for peerID, stats := range m.Rtts {
|
||||||
|
peerName := peerID
|
||||||
|
if name, ok := machineNames[peerID]; ok {
|
||||||
|
peerName = name
|
||||||
|
}
|
||||||
|
rows = append(rows, row{
|
||||||
|
machine: m.Machine.Name,
|
||||||
|
peer: peerName,
|
||||||
|
median: stats.Median.AsDuration(),
|
||||||
|
stdDev: stats.StdDev.AsDuration(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by machine name then peer name.
|
||||||
|
sort.Slice(rows, func(i, j int) bool {
|
||||||
|
if rows[i].machine == rows[j].machine {
|
||||||
|
return rows[i].peer < rows[j].peer
|
||||||
|
}
|
||||||
|
return rows[i].machine < rows[j].machine
|
||||||
|
})
|
||||||
|
|
||||||
|
// Print table.
|
||||||
|
t := tui.NewTable()
|
||||||
|
t.Headers("MACHINE", "PEER", "MEDIAN", "STDDEV")
|
||||||
|
|
||||||
|
for _, r := range rows {
|
||||||
|
t.Row(r.machine, r.peer, tui.FormatRTT(r.median), formatRTTStdDev(r.stdDev))
|
||||||
|
}
|
||||||
|
|
||||||
|
lipgloss.Println(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatRTTStdDev formats a round-trip time standard deviation with one decimal place, e.g. "±19.4ms".
|
||||||
|
func formatRTTStdDev(d time.Duration) string {
|
||||||
|
return fmt.Sprintf("±%.1fms", float64(d)/float64(time.Millisecond))
|
||||||
|
}
|
||||||
@@ -6,8 +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/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -46,6 +47,13 @@ At least one flag must be specified to perform an update.`,
|
|||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return update(cmd.Context(), uncli, cmd, opts, args[0])
|
return update(cmd.Context(), uncli, cmd, opts, args[0])
|
||||||
},
|
},
|
||||||
|
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.Machines(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
@@ -59,9 +67,11 @@ At least one flag must be specified to perform an update.`,
|
|||||||
)
|
)
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&opts.wgEndpoints, "wg-endpoint", nil,
|
&opts.wgEndpoints, "wg-endpoint", nil,
|
||||||
fmt.Sprintf("WireGuard endpoint address in format: IP, IP:PORT, IPv6, or [IPv6]:PORT. "+
|
fmt.Sprintf("WireGuard endpoint address that other machines in the cluster should use to establish "+
|
||||||
"Default port %d is used if omitted.\n", network.WireGuardPort)+
|
"WireGuard connections\n"+
|
||||||
"Other machines in the cluster will use this endpoint to establish a WireGuard connection to this 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.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.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -80,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
|
||||||
@@ -112,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
|
||||||
}
|
}
|
||||||
@@ -122,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)
|
||||||
}
|
}
|
||||||
@@ -2,25 +2,29 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"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"
|
||||||
"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/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 {
|
||||||
@@ -36,34 +40,45 @@ 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")
|
||||||
|
|
||||||
var conn *config.MachineConnection
|
var conn *config.MachineConnection
|
||||||
if opts.connect != "" {
|
if opts.connect != "" {
|
||||||
if strings.HasPrefix(opts.connect, "tcp://") {
|
if after, ok := strings.CutPrefix(opts.connect, "tcp://"); ok {
|
||||||
addrPort, err := netip.ParseAddrPort(opts.connect[len("tcp://"):])
|
addrPort, err := netip.ParseAddrPort(after)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse TCP address: %w", err)
|
return fmt.Errorf("parse TCP address: %w", err)
|
||||||
}
|
}
|
||||||
conn = &config.MachineConnection{
|
conn = &config.MachineConnection{
|
||||||
TCP: &addrPort,
|
TCP: &addrPort,
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(opts.connect, "ssh+cli://") {
|
} else if after, ok := strings.CutPrefix(opts.connect, "ssh+go://"); ok {
|
||||||
dest := opts.connect[len("ssh+cli://"):]
|
|
||||||
conn = &config.MachineConnection{
|
conn = &config.MachineConnection{
|
||||||
SSHCLI: config.SSHDestination(dest),
|
SSHGo: config.SSHDestination(after),
|
||||||
|
}
|
||||||
|
} else if after, ok := strings.CutPrefix(opts.connect, "ssh+cli://"); ok {
|
||||||
|
// Backward-compatible alias for ssh://.
|
||||||
|
conn = &config.MachineConnection{
|
||||||
|
SSH: config.SSHDestination(after),
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(opts.connect, "unix://") {
|
} else if strings.HasPrefix(opts.connect, "unix://") {
|
||||||
conn = &config.MachineConnection{
|
conn = &config.MachineConnection{
|
||||||
Unix: opts.connect[len("unix://"):],
|
Unix: opts.connect[len("unix://"):],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Default: system ssh CLI command (no prefix or ssh:// prefix).
|
||||||
dest := strings.TrimPrefix(opts.connect, "ssh://")
|
dest := strings.TrimPrefix(opts.connect, "ssh://")
|
||||||
conn = &config.MachineConnection{
|
conn = &config.MachineConnection{
|
||||||
SSH: config.SSHDestination(dest),
|
SSH: config.SSHDestination(dest),
|
||||||
@@ -72,6 +87,16 @@ 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 !fs.Exists(configPath) && fs.Exists(machine.DefaultClusterAPISockPath) {
|
||||||
|
conn = &config.MachineConnection{
|
||||||
|
Unix: machine.DefaultClusterAPISockPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uncli, err := cli.New(configPath, conn, opts.context)
|
uncli, err := cli.New(configPath, conn, opts.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initialise CLI: %w", err)
|
return fmt.Errorf("initialise CLI: %w", err)
|
||||||
@@ -83,7 +108,7 @@ func main() {
|
|||||||
|
|
||||||
cmd.PersistentFlags().StringVar(&opts.connect, "connect", "",
|
cmd.PersistentFlags().StringVar(&opts.connect, "connect", "",
|
||||||
"Connect to a remote cluster machine without using the Uncloud configuration file. [$UNCLOUD_CONNECT]\n"+
|
"Connect to a remote cluster machine without using the Uncloud configuration file. [$UNCLOUD_CONNECT]\n"+
|
||||||
"Format: [ssh://]user@host[:port], ssh+cli://user@host[:port], tcp://host:port, or unix:///path/to/uncloud.sock")
|
"Format: [ssh://]user@host[:port], ssh+go://user@host[:port], tcp://host:port, or unix:///path/to/uncloud.sock")
|
||||||
cmd.PersistentFlags().StringVar(&opts.configPath, "uncloud-config", "~/.config/uncloud/config.yaml",
|
cmd.PersistentFlags().StringVar(&opts.configPath, "uncloud-config", "~/.config/uncloud/config.yaml",
|
||||||
"Path to the Uncloud configuration file. [$UNCLOUD_CONFIG]")
|
"Path to the Uncloud configuration file. [$UNCLOUD_CONFIG]")
|
||||||
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml")
|
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml")
|
||||||
@@ -96,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))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -119,11 +140,12 @@ func main() {
|
|||||||
NewDocsCommand(),
|
NewDocsCommand(),
|
||||||
NewImagesCommand(),
|
NewImagesCommand(),
|
||||||
NewPsCommand(),
|
NewPsCommand(),
|
||||||
|
NewProxyCommand(),
|
||||||
caddy.NewRootCommand(),
|
caddy.NewRootCommand(),
|
||||||
cmdcontext.NewRootCommand(),
|
cmdcontext.NewRootCommand(),
|
||||||
dns.NewRootCommand(),
|
dns.NewRootCommand(),
|
||||||
image.NewRootCommand(),
|
image.NewRootCommand(),
|
||||||
machine.NewRootCommand(),
|
cmdmachine.NewRootCommand(),
|
||||||
service.NewRootCommand(),
|
service.NewRootCommand(),
|
||||||
service.NewExecCommand("service"),
|
service.NewExecCommand("service"),
|
||||||
service.NewInspectCommand("service"),
|
service.NewInspectCommand("service"),
|
||||||
@@ -134,8 +156,39 @@ 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(),
|
||||||
)
|
)
|
||||||
cobra.CheckErr(cmd.Execute())
|
if err := cmd.Execute(); err != nil {
|
||||||
|
if cancelled, ok := errors.AsType[*cli.CancelledError](err); ok {
|
||||||
|
fmt.Fprintln(os.Stderr, cancelled.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
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,16 +6,14 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"charm.land/huh/v2/spinner"
|
|
||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"charm.land/lipgloss/v2/table"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -67,12 +65,13 @@ type containerInfo struct {
|
|||||||
serviceName string
|
serviceName string
|
||||||
machineName string
|
machineName string
|
||||||
id string
|
id string
|
||||||
name string
|
|
||||||
image string
|
image string
|
||||||
status string
|
status string
|
||||||
highlight containerHighlight
|
highlight containerHighlight
|
||||||
created time.Time
|
created time.Time
|
||||||
ip string
|
ip string
|
||||||
|
// Hook type (e.g., "pre-deploy"), empty for regular containers.
|
||||||
|
hook string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
||||||
@@ -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...").
|
|
||||||
Type(spinner.MiniDot).
|
|
||||||
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
|
|
||||||
return &spinner.Styles{
|
|
||||||
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
|
|
||||||
Title: lipgloss.NewStyle(),
|
|
||||||
}
|
|
||||||
})).
|
|
||||||
ActionWithErr(func(ctx context.Context) error {
|
|
||||||
containers, err = collectContainers(ctx, clusterClient)
|
containers, err = collectContainers(ctx, clusterClient)
|
||||||
return err
|
return err
|
||||||
}).
|
})
|
||||||
Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("collect containers: %w", err)
|
return fmt.Errorf("collect containers: %w", err)
|
||||||
}
|
}
|
||||||
@@ -132,24 +121,22 @@ func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printContainers(containers []containerInfo) error {
|
func printContainers(containers []containerInfo) error {
|
||||||
t := table.New().
|
t := tui.NewTable()
|
||||||
// Remove the default border.
|
|
||||||
Border(lipgloss.Border{}).
|
|
||||||
BorderTop(false).
|
|
||||||
BorderBottom(false).
|
|
||||||
BorderLeft(false).
|
|
||||||
BorderRight(false).
|
|
||||||
BorderHeader(false).
|
|
||||||
BorderColumn(false).
|
|
||||||
StyleFunc(func(row, col int) lipgloss.Style {
|
|
||||||
if row == table.HeaderRow {
|
|
||||||
return lipgloss.NewStyle().Bold(true).PaddingRight(3)
|
|
||||||
}
|
|
||||||
// Regular style for data rows with padding.
|
|
||||||
return lipgloss.NewStyle().PaddingRight(3)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Headers("SERVICE", "CONTAINER ID", "CONTAINER NAME", "IMAGE", "CREATED", "STATUS", "IP ADDRESS", "MACHINE")
|
// Show HOOK column only when hook containers are present.
|
||||||
|
hasHooks := false
|
||||||
|
for _, ctr := range containers {
|
||||||
|
if ctr.hook != "" {
|
||||||
|
hasHooks = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasHooks {
|
||||||
|
t.Headers("SERVICE", "CONTAINER ID", "IMAGE", "CREATED", "STATUS", "HOOK", "IP ADDRESS", "MACHINE")
|
||||||
|
} else {
|
||||||
|
t.Headers("SERVICE", "CONTAINER ID", "IMAGE", "CREATED", "STATUS", "IP ADDRESS", "MACHINE")
|
||||||
|
}
|
||||||
|
|
||||||
for _, ctr := range containers {
|
for _, ctr := range containers {
|
||||||
id := ctr.id
|
id := ctr.id
|
||||||
@@ -171,35 +158,36 @@ func printContainers(containers []containerInfo) error {
|
|||||||
statusStyle = lipgloss.NewStyle() // Default
|
statusStyle = lipgloss.NewStyle() // Default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasHooks {
|
||||||
t.Row(
|
t.Row(
|
||||||
ctr.serviceName,
|
ctr.serviceName,
|
||||||
id,
|
id,
|
||||||
ctr.name,
|
tui.FormatImage(ctr.image, tui.NoStyle),
|
||||||
ctr.image,
|
created,
|
||||||
|
statusStyle.Render(ctr.status),
|
||||||
|
ctr.hook,
|
||||||
|
ctr.ip,
|
||||||
|
ctr.machineName,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Row(
|
||||||
|
ctr.serviceName,
|
||||||
|
id,
|
||||||
|
tui.FormatImage(ctr.image, tui.NoStyle),
|
||||||
created,
|
created,
|
||||||
statusStyle.Render(ctr.status),
|
statusStyle.Render(ctr.status),
|
||||||
ctr.ip,
|
ctr.ip,
|
||||||
ctr.machineName,
|
ctr.machineName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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(
|
||||||
@@ -211,33 +199,21 @@ 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")
|
||||||
}
|
|
||||||
|
|
||||||
machineName := "unknown"
|
|
||||||
if msc.Metadata != nil {
|
|
||||||
var ok bool
|
|
||||||
machineName, ok = machinesNamesByIP[msc.Metadata.Machine]
|
|
||||||
if !ok {
|
|
||||||
// Fallback to machine's IP as name.
|
|
||||||
machineName = msc.Metadata.Machine
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fallback to the first available machine name.
|
|
||||||
if len(machines) > 0 {
|
|
||||||
machineName = machines[0].Machine.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if msc.Metadata != nil && msc.Metadata.Error != "" {
|
|
||||||
tui.PrintWarning(fmt.Sprintf("failed to list containers on machine %s: %s", machineName,
|
|
||||||
msc.Metadata.Error))
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ctr := range msc.Containers {
|
machineName := msc.Metadata.MachineName
|
||||||
|
|
||||||
|
if msc.Metadata.Error != "" {
|
||||||
|
tui.PrintWarning(fmt.Sprintf("failed to list service containers on machine %s: %s",
|
||||||
|
machineName, msc.Metadata.Error))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ctr := range append(msc.Containers, msc.HookContainers...) {
|
||||||
if ctr.Container.State == nil || ctr.Container.Config == nil {
|
if ctr.Container.State == nil || ctr.Container.Config == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -259,6 +235,9 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
|
|||||||
highlight = highlightSuccess
|
highlight = highlightSuccess
|
||||||
} else if ctr.Container.State.Status == "running" {
|
} else if ctr.Container.State.Status == "running" {
|
||||||
highlight = highlightNormal
|
highlight = highlightNormal
|
||||||
|
} else if ctr.IsHook() && ctr.Container.State.Status == "exited" && ctr.Container.State.ExitCode == 0 {
|
||||||
|
// Hook containers (e.g., pre-deploy) are expected to exit successfully.
|
||||||
|
highlight = highlightNormal
|
||||||
} else { // Other non-critical but noteworthy states
|
} else { // Other non-critical but noteworthy states
|
||||||
highlight = highlightWarning
|
highlight = highlightWarning
|
||||||
}
|
}
|
||||||
@@ -276,12 +255,12 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
|
|||||||
serviceName: ctr.ServiceName(),
|
serviceName: ctr.ServiceName(),
|
||||||
machineName: machineName,
|
machineName: machineName,
|
||||||
id: ctr.Container.ID,
|
id: ctr.Container.ID,
|
||||||
name: ctr.Container.Name,
|
|
||||||
image: ctr.Container.Config.Image,
|
image: ctr.Container.Config.Image,
|
||||||
status: status,
|
status: status,
|
||||||
highlight: highlight,
|
highlight: highlight,
|
||||||
created: created,
|
created: created,
|
||||||
ip: ipStr,
|
ip: ipStr,
|
||||||
|
hook: ctr.Config.Labels[api.LabelHook],
|
||||||
}
|
}
|
||||||
containers = append(containers, info)
|
containers = append(containers, info)
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"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/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -48,14 +49,20 @@ If the service has multiple replicas and no container ID is specified, the comma
|
|||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(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)
|
||||||
serviceName := args[0]
|
serviceName, command := normalizeExecArgs(args)
|
||||||
command := args[1:]
|
|
||||||
if len(command) == 0 {
|
if len(command) == 0 {
|
||||||
command = DEFAULT_COMMAND
|
command = DEFAULT_COMMAND
|
||||||
}
|
}
|
||||||
return runExec(cmd.Context(), uncli, serviceName, command, opts)
|
return runExec(cmd.Context(), uncli, serviceName, command, opts)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
|
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)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
execCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background")
|
execCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background")
|
||||||
@@ -82,6 +89,15 @@ If the service has multiple replicas and no container ID is specified, the comma
|
|||||||
return execCmd
|
return execCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeExecArgs(args []string) (serviceName string, command []string) {
|
||||||
|
serviceName = args[0]
|
||||||
|
command = args[1:]
|
||||||
|
if len(command) > 0 && command[0] == "--" {
|
||||||
|
command = command[1:]
|
||||||
|
}
|
||||||
|
return serviceName, command
|
||||||
|
}
|
||||||
|
|
||||||
func runExec(ctx context.Context, uncli *cli.CLI, serviceName string, command []string, opts execCliOptions) error {
|
func runExec(ctx context.Context, uncli *cli.CLI, serviceName string, command []string, opts execCliOptions) error {
|
||||||
// Disable TTY allocation if not connected to a terminal
|
// Disable TTY allocation if not connected to a terminal
|
||||||
if !tui.IsStdoutTerminal() {
|
if !tui.IsStdoutTerminal() {
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormalizeExecArgs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
wantServiceName string
|
||||||
|
wantCommand []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "service only",
|
||||||
|
args: []string{"test-service"},
|
||||||
|
wantServiceName: "test-service",
|
||||||
|
wantCommand: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service with command",
|
||||||
|
args: []string{"test-service", "echo", "hello"},
|
||||||
|
wantServiceName: "test-service",
|
||||||
|
wantCommand: []string{"echo", "hello"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service with separator and command",
|
||||||
|
args: []string{"test-service", "--", "echo", "hello"},
|
||||||
|
wantServiceName: "test-service",
|
||||||
|
wantCommand: []string{"echo", "hello"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service with separator only",
|
||||||
|
args: []string{"test-service", "--"},
|
||||||
|
wantServiceName: "test-service",
|
||||||
|
wantCommand: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "separator preserves command flag",
|
||||||
|
args: []string{"test-service", "--", "--help"},
|
||||||
|
wantServiceName: "test-service",
|
||||||
|
wantCommand: []string{"--help"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only first separator is removed",
|
||||||
|
args: []string{"test-service", "--", "cmd", "--", "arg"},
|
||||||
|
wantServiceName: "test-service",
|
||||||
|
wantCommand: []string{"cmd", "--", "arg"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotServiceName, gotCommand := normalizeExecArgs(tt.args)
|
||||||
|
assert.Equal(t, tt.wantServiceName, gotServiceName)
|
||||||
|
assert.Equal(t, tt.wantCommand, gotCommand)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,15 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
"text/tabwriter"
|
|
||||||
"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"
|
||||||
|
"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/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,13 @@ func NewInspectCommand(groupID string) *cobra.Command {
|
|||||||
return inspect(cmd.Context(), uncli, opts)
|
return inspect(cmd.Context(), uncli, opts)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
|
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
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -47,42 +55,41 @@ 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)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
|
// Combine regular and hook containers.
|
||||||
|
allContainers := append(svc.Containers, svc.HookContainers...)
|
||||||
|
|
||||||
// Parse created times for sorting and display.
|
// Parse created times for sorting and display.
|
||||||
createdTimes := make(map[string]time.Time, len(svc.Containers))
|
createdTimes := make(map[string]time.Time, len(allContainers))
|
||||||
for _, ctr := range svc.Containers {
|
for _, ctr := range allContainers {
|
||||||
createdTimes[ctr.Container.ID], _ = time.Parse(time.RFC3339Nano, ctr.Container.Created)
|
createdTimes[ctr.Container.ID], _ = time.Parse(time.RFC3339Nano, ctr.Container.Created)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort containers by created time (newest first).
|
// Sort containers by created time (newest first).
|
||||||
slices.SortFunc(svc.Containers, func(a, b api.MachineServiceContainer) int {
|
slices.SortFunc(allContainers, func(a, b api.MachineServiceContainer) int {
|
||||||
return createdTimes[b.Container.ID].Compare(createdTimes[a.Container.ID])
|
return createdTimes[b.Container.ID].Compare(createdTimes[a.Container.ID])
|
||||||
})
|
})
|
||||||
|
|
||||||
// Print the list of containers in a table format.
|
// Print the list of containers in a table format.
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
// Show HOOK column only when hook containers are present.
|
||||||
if _, err = fmt.Fprintln(tw, "CONTAINER ID\tIMAGE\tCREATED\tSTATUS\tIP ADDRESS\tMACHINE"); err != nil {
|
hasHooks := len(svc.HookContainers) > 0
|
||||||
return fmt.Errorf("write header: %w", err)
|
|
||||||
|
t := tui.NewTable()
|
||||||
|
if hasHooks {
|
||||||
|
t.Headers("CONTAINER ID", "IMAGE", "CREATED", "STATUS", "HOOK", "IP ADDRESS", "MACHINE")
|
||||||
|
} else {
|
||||||
|
t.Headers("CONTAINER ID", "IMAGE", "CREATED", "STATUS", "IP ADDRESS", "MACHINE")
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
for _, ctr := range svc.Containers {
|
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
|
||||||
}
|
}
|
||||||
@@ -98,19 +105,28 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
ipStr = ip.String()
|
ipStr = ip.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprintf(
|
if hasHooks {
|
||||||
tw,
|
t.Row(
|
||||||
"%s\t%s\t%s\t%s\t%s\t%s\n",
|
|
||||||
stringid.TruncateID(ctr.Container.ID),
|
stringid.TruncateID(ctr.Container.ID),
|
||||||
ctr.Container.Config.Image,
|
tui.FormatImage(ctr.Container.Config.Image, tui.NoStyle),
|
||||||
|
created,
|
||||||
|
state,
|
||||||
|
ctr.Container.Config.Labels[api.LabelHook],
|
||||||
|
ipStr,
|
||||||
|
machine,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Row(
|
||||||
|
stringid.TruncateID(ctr.Container.ID),
|
||||||
|
tui.FormatImage(ctr.Container.Config.Image, tui.NoStyle),
|
||||||
created,
|
created,
|
||||||
state,
|
state,
|
||||||
ipStr,
|
ipStr,
|
||||||
machine,
|
machine,
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("write row: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tw.Flush()
|
|
||||||
|
lipgloss.Println(t)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
|
"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/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/psviderski/uncloud/pkg/client/compose"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLogsCommand(groupID string) *cobra.Command {
|
||||||
|
var options logs.Options
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "logs [SERVICE[/CONTAINER]...]",
|
||||||
|
Aliases: []string{"log"},
|
||||||
|
Short: "View service logs.",
|
||||||
|
Long: `View logs from all replicas of the specified service(s) across all machines in the cluster.
|
||||||
|
|
||||||
|
To view logs from specific replicas (containers) within a service, use the SERVICE/CONTAINER form,
|
||||||
|
where CONTAINER is a container name, full ID, or unique ID prefix.
|
||||||
|
|
||||||
|
If no services are specified, streams logs from all services defined in the Compose file
|
||||||
|
(compose.yaml by default or the file(s) specified with --file).`,
|
||||||
|
Example: ` # View recent logs for a service.
|
||||||
|
uc logs web
|
||||||
|
|
||||||
|
# Stream logs in real-time (follow mode).
|
||||||
|
uc logs -f web
|
||||||
|
|
||||||
|
# View logs from multiple services.
|
||||||
|
uc logs web api db
|
||||||
|
|
||||||
|
# View logs from all services in compose.yaml.
|
||||||
|
uc logs
|
||||||
|
|
||||||
|
# Show last 20 lines per replica (default is 100).
|
||||||
|
uc logs -n 20 web
|
||||||
|
|
||||||
|
# Show all logs without line limit.
|
||||||
|
uc logs -n all web
|
||||||
|
|
||||||
|
# View logs from a specific time range.
|
||||||
|
uc logs --since 3h --until 1h30m web
|
||||||
|
|
||||||
|
# View logs only from specific replicas (containers).
|
||||||
|
uc logs web/61d57fd3428f api/2f60
|
||||||
|
|
||||||
|
# View logs only from replicas running on specific machines.
|
||||||
|
uc logs -m machine1,machine2 web api`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return RunLogs(cmd.Context(), uncli, args, options)
|
||||||
|
},
|
||||||
|
GroupID: groupID,
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return completion.Services(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringSliceVar(&options.Files, "file", nil,
|
||||||
|
"One or more Compose files to load service names from when no services are specified. (default compose.yaml)")
|
||||||
|
|
||||||
|
cmd.Flags().AddFlagSet(logs.Flags(&options))
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunLogs(ctx context.Context, uncli *cli.CLI, args []string, opts logs.Options) error {
|
||||||
|
serviceArgs, err := logs.ParseServiceArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no services specified, try to load them from the Compose file(s).
|
||||||
|
fromCompose := false
|
||||||
|
if len(serviceArgs) == 0 {
|
||||||
|
fromCompose = true
|
||||||
|
project, err := compose.LoadProject(ctx, opts.Files)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load Compose file(s): %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uncli.SetClusterContextIfUnset(compose.ClusterContext(project))
|
||||||
|
|
||||||
|
// View logs for all services, including disabled by inactive profiles.
|
||||||
|
composeServices := append(project.ServiceNames(), project.DisabledServiceNames()...)
|
||||||
|
if len(composeServices) == 0 {
|
||||||
|
return errors.New("no services found in Compose file(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceArgs = make([]logs.ServiceArg, len(composeServices))
|
||||||
|
for i, name := range composeServices {
|
||||||
|
serviceArgs[i] = logs.ServiceArg{Service: name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse tail option.
|
||||||
|
tail, err := logs.Tail(opts.Tail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
baseOpts := api.ServiceLogsOptions{
|
||||||
|
Follow: opts.Follow,
|
||||||
|
Tail: tail,
|
||||||
|
Since: opts.Since,
|
||||||
|
Until: opts.Until,
|
||||||
|
Machines: cli.ExpandCommaSeparatedValues(opts.Machines),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect log streams from all services. When service names come from a Compose file,
|
||||||
|
// skip the ones that are not found in the cluster (they may have been removed or not deployed yet).
|
||||||
|
machineIDsSet := mapset.NewSet[string]()
|
||||||
|
svcStreams := make([]<-chan api.ServiceLogEntry, 0, len(serviceArgs))
|
||||||
|
var foundServices, notFoundServices []string
|
||||||
|
|
||||||
|
for _, sa := range serviceArgs {
|
||||||
|
svcOpts := baseOpts
|
||||||
|
svcOpts.Containers = sa.Containers
|
||||||
|
|
||||||
|
svc, ch, err := c.ServiceLogs(ctx, sa.Service, svcOpts)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrNotFound) && fromCompose {
|
||||||
|
notFoundServices = append(notFoundServices, sa.Service)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("stream logs for service '%s': %w", sa.Service, err)
|
||||||
|
}
|
||||||
|
svcStreams = append(svcStreams, ch)
|
||||||
|
foundServices = append(foundServices, sa.Service)
|
||||||
|
|
||||||
|
machineIDs := svc.MachineIDs()
|
||||||
|
machineIDsSet.Append(machineIDs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fromCompose {
|
||||||
|
if len(foundServices) == 0 {
|
||||||
|
return fmt.Errorf("stream logs for services defined in %s: no services found in the cluster",
|
||||||
|
strings.Join(opts.Files, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range notFoundServices {
|
||||||
|
tui.PrintWarning(fmt.Sprintf("service '%s' not found in the cluster, skipping", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream <-chan api.ServiceLogEntry
|
||||||
|
if len(svcStreams) == 1 {
|
||||||
|
stream = svcStreams[0]
|
||||||
|
} else {
|
||||||
|
// Merge all service streams into a single sorted stream without stall detection as its handled per-service.
|
||||||
|
merger := client.NewLogMerger(svcStreams, client.LogMergerOptions{})
|
||||||
|
stream = merger.Stream()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch machine names for all machines (machineIDsSet) service containers are running on.
|
||||||
|
// Note: this is the full set per service, not narrowed by --machine or per-container filters,
|
||||||
|
// so the formatter may pad columns wider than strictly needed when filters are active.
|
||||||
|
machines, err := c.ListMachines(ctx, &api.MachineFilter{NamesOrIDs: machineIDsSet.ToSlice()})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("list machines: %w", err)
|
||||||
|
}
|
||||||
|
machineNames := make([]string, 0, len(machines))
|
||||||
|
for _, m := range machines {
|
||||||
|
machineNames = append(machineNames, m.Machine.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
formatter := logs.NewFormatter(machineNames, foundServices, opts.UTC)
|
||||||
|
|
||||||
|
// Print merged logs.
|
||||||
|
for entry := range stream {
|
||||||
|
formatter.PrintEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -3,12 +3,12 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
|
"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/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -50,20 +50,22 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Print the list of services in a table format.
|
// Print the list of services in a table format.
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
t := tui.NewTable()
|
||||||
|
|
||||||
// Include the ID column if there are duplicate service names to differentiate them.
|
// Include the ID column if there are duplicate service names to differentiate them.
|
||||||
|
headers := []string{"NAME", "MODE", "REPLICAS", "IMAGE", "ENDPOINTS"}
|
||||||
if haveDuplicateNames {
|
if haveDuplicateNames {
|
||||||
if _, err = fmt.Fprintf(tw, "ID\t"); err != nil {
|
headers = append([]string{"ID"}, headers...)
|
||||||
return fmt.Errorf("write header: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err = fmt.Fprintln(tw, "NAME\tMODE\tREPLICAS\tIMAGE\tENDPOINTS"); err != nil {
|
|
||||||
return fmt.Errorf("write header: %w", err)
|
|
||||||
}
|
}
|
||||||
|
t.Headers(headers...)
|
||||||
|
|
||||||
for _, s := range services {
|
for _, s := range services {
|
||||||
images := strings.Join(s.Images(), ", ")
|
images := s.Images()
|
||||||
endpoints := strings.Join(s.Endpoints(), ", ")
|
for i, img := range images {
|
||||||
|
images[i] = tui.FormatImage(img, tui.NoStyle)
|
||||||
|
}
|
||||||
|
formattedImages := strings.Join(images, tui.Faint.Render(", "))
|
||||||
|
endpoints := strings.Join(s.Endpoints(), tui.Faint.Render(", "))
|
||||||
|
|
||||||
// If no endpoints from ports, check if the service uses custom Caddy config.
|
// If no endpoints from ports, check if the service uses custom Caddy config.
|
||||||
if endpoints == "" {
|
if endpoints == "" {
|
||||||
@@ -74,15 +76,13 @@ func list(ctx context.Context, uncli *cli.CLI) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row := []string{s.Name, s.Mode, fmt.Sprintf("%d", len(s.Containers)), formattedImages, endpoints}
|
||||||
if haveDuplicateNames {
|
if haveDuplicateNames {
|
||||||
if _, err = fmt.Fprintf(tw, "%s\t", s.ID); err != nil {
|
row = append([]string{s.ID}, row...)
|
||||||
return fmt.Errorf("write row: %w", err)
|
|
||||||
}
|
}
|
||||||
|
t.Row(row...)
|
||||||
}
|
}
|
||||||
if _, err = fmt.Fprintf(tw, "%s\t%s\t%d\t%s\t%s\n",
|
|
||||||
s.Name, s.Mode, len(s.Containers), images, endpoints); err != nil {
|
lipgloss.Println(t)
|
||||||
return fmt.Errorf("write row: %w", err)
|
return nil
|
||||||
}
|
|
||||||
}
|
|
||||||
return tw.Flush()
|
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,6 +32,10 @@ directives in image Dockerfiles) are automatically removed with their containers
|
|||||||
return rm(cmd.Context(), uncli, opts)
|
return rm(cmd.Context(), uncli, opts)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return completion.Services(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/docker/docker/daemon/names"
|
"github.com/docker/docker/daemon/names"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
@@ -92,7 +93,7 @@ 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 (default is tcp). If a hostname for http(s) port is not specified\n"+
|
||||||
"and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+
|
"and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+
|
||||||
"Examples:\n"+
|
"Examples:\n"+
|
||||||
@@ -100,7 +101,9 @@ func NewRunCommand(groupID string) *cobra.Command {
|
|||||||
" -p app.example.com:8080/https Publish port 8080 as HTTPS via reverse proxy with custom hostname\n"+
|
" -p app.example.com:8080/https Publish port 8080 as HTTPS via reverse proxy with custom hostname\n"+
|
||||||
// TODO: add support for publishing L4 tcp/udp ports.
|
// TODO: add support for publishing L4 tcp/udp ports.
|
||||||
//" -p 9000:8080 Publish port 8080 as TCP port 9000 via reverse proxy\n"+
|
//" -p 9000:8080 Publish port 8080 as TCP port 9000 via reverse proxy\n"+
|
||||||
" -p 53:5353/udp@host Bind UDP port 5353 to host port 53")
|
" -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))
|
||||||
@@ -119,11 +122,14 @@ 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"+
|
||||||
" -v /host/path:/container/path:ro Bind mount a host directory or file as read-only")
|
" -v /host/path:/container/path:ro Bind mount a host directory or file as read-only")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"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/internal/cli"
|
"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/cli/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -29,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]
|
||||||
@@ -44,6 +47,13 @@ func NewScaleCommand(groupID string) *cobra.Command {
|
|||||||
return scale(cmd.Context(), uncli, opts)
|
return scale(cmd.Context(), uncli, opts)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
|
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().BoolVarP(&opts.yes, "yes", "y", false,
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
@@ -128,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")
|
||||||
}
|
}
|
||||||
@@ -147,8 +157,7 @@ func scale(ctx context.Context, uncli *cli.CLI, opts scaleOptions) error {
|
|||||||
return fmt.Errorf("confirm scaling: %w", err)
|
return fmt.Errorf("confirm scaling: %w", err)
|
||||||
}
|
}
|
||||||
if !confirmed {
|
if !confirmed {
|
||||||
fmt.Println("Cancelled. No changes were made.")
|
return cli.Cancelled("Scaling cancelled. No changes were made.")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +31,10 @@ Services can be specified by name or ID.`,
|
|||||||
return start(cmd.Context(), uncli, opts)
|
return start(cmd.Context(), uncli, opts)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return completion.Services(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"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/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +34,10 @@ Services can be specified by name or ID. Stopped services can be restarted with
|
|||||||
return stop(cmd.Context(), uncli, opts)
|
return stop(cmd.Context(), uncli, opts)
|
||||||
},
|
},
|
||||||
GroupID: groupID,
|
GroupID: groupID,
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return completion.Services(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVarP(&opts.signal, "signal", "s", "",
|
cmd.Flags().StringVarP(&opts.signal, "signal", "s", "",
|
||||||
"Signal to send to each container's main process.\n"+
|
"Signal to send to each container's main process.\n"+
|
||||||
@@ -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,8 +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/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -50,6 +51,8 @@ func NewCreateCommand() *cobra.Command {
|
|||||||
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||||
"Name or ID of the machine to create the volume on.")
|
"Name or ID of the machine to create the volume on.")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -26,12 +27,21 @@ func NewInspectCommand() *cobra.Command {
|
|||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return inspect(cmd.Context(), uncli, args[0], opts)
|
return inspect(cmd.Context(), uncli, args[0], 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.Volumes(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||||
"Name or ID of the machine where the volume is located. "+
|
"Name or ID of the machine where the volume is located. "+
|
||||||
"If not specified, the volume will be searched across all machines.")
|
"If not specified, the volume will be searched across all machines.")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,12 +3,13 @@ package volume
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
|
"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/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -37,6 +38,8 @@ func NewListCommand() *cobra.Command {
|
|||||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false,
|
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false,
|
||||||
"Only display volume names.")
|
"Only display volume names.")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,16 +89,13 @@ func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print the volumes in a table format.
|
// Print the volumes in a table format.
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
t := tui.NewTable()
|
||||||
fmt.Fprintln(tw, "NAME\tDRIVER\tMACHINE")
|
t.Headers("NAME", "DRIVER", "MACHINE")
|
||||||
|
|
||||||
for _, v := range volumes {
|
for _, v := range volumes {
|
||||||
fmt.Fprintf(tw, "%s\t%s\t%s\n",
|
t.Row(v.Volume.Name, v.Volume.Driver, v.MachineName)
|
||||||
v.Volume.Name,
|
|
||||||
v.Volume.Driver,
|
|
||||||
v.MachineName,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tw.Flush()
|
lipgloss.Println(t)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"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/tui"
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -30,6 +31,10 @@ func NewRemoveCommand() *cobra.Command {
|
|||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return remove(cmd.Context(), uncli, args, opts)
|
return remove(cmd.Context(), uncli, args, opts)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
return completion.Volumes(cmd.Context(), uncli, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().BoolVarP(&opts.force, "force", "f", false,
|
cmd.Flags().BoolVarP(&opts.force, "force", "f", false,
|
||||||
@@ -41,6 +46,8 @@ func NewRemoveCommand() *cobra.Command {
|
|||||||
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
|
||||||
"Do not prompt for confirmation before removing the volume(s).")
|
"Do not prompt for confirmation before removing the volume(s).")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File renamed without changes.
@@ -3,13 +3,15 @@ package wg
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
|
"github.com/psviderski/uncloud/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -44,6 +46,9 @@ func newShowCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||||
"Name or ID of the machine to show the configuration for. (default is connected machine)")
|
"Name or ID of the machine to show the configuration for. (default is connected machine)")
|
||||||
|
|
||||||
|
completion.MachinesFlag(cmd)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +61,7 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
|||||||
|
|
||||||
if opts.machine != "" {
|
if opts.machine != "" {
|
||||||
// Proxy requests to the specified machine.
|
// Proxy requests to the specified machine.
|
||||||
ctx, _, err = client.ProxyMachinesContext(ctx, []string{opts.machine})
|
ctx = client.ProxySingleMachineContext(ctx, opts.machine)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.MachineClient.InspectWireGuardNetwork(ctx, nil)
|
resp, err := client.MachineClient.InspectWireGuardNetwork(ctx, nil)
|
||||||
@@ -75,16 +77,18 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("list machines: %w", err)
|
return fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
machinesNamesByPublicKey := make(map[string]string)
|
machinesByPublicKey := make(map[string]*pb.MachineInfo)
|
||||||
for _, m := range machines {
|
for _, m := range machines {
|
||||||
publicKey := wgtypes.Key(m.Machine.Network.PublicKey).String()
|
publicKey := wgtypes.Key(m.Machine.Network.PublicKey).String()
|
||||||
machinesNamesByPublicKey[publicKey] = m.Machine.Name
|
machinesByPublicKey[publicKey] = m.Machine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the machine's name for more descriptive output
|
// Fetch the machine's info and RTTs for display.
|
||||||
|
var selfMachine *pb.MachineDetails
|
||||||
inspectResp, err := client.MachineClient.InspectMachine(ctx, nil)
|
inspectResp, err := client.MachineClient.InspectMachine(ctx, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Machine name: %s\n", inspectResp.Machines[0].Machine.Name)
|
selfMachine = inspectResp.Machines[0]
|
||||||
|
fmt.Printf("Machine name: %s\n", selfMachine.Machine.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("WireGuard interface: %s\n", resp.InterfaceName)
|
fmt.Printf("WireGuard interface: %s\n", resp.InterfaceName)
|
||||||
@@ -97,15 +101,20 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
t := tui.NewTable()
|
||||||
if _, err = fmt.Fprintln(tw, "PEER\tPUBLIC KEY\tENDPOINT\tHANDSHAKE\tRECEIVED\tSENT\tALLOWED IPS"); err != nil {
|
t.Headers("PEER", "PUBLIC KEY", "ENDPOINT", "HANDSHAKE", "RTT", "RECEIVED", "SENT", "ALLOWED IPS")
|
||||||
return fmt.Errorf("write header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, peer := range resp.Peers {
|
for _, peer := range resp.Peers {
|
||||||
machineName, ok := machinesNamesByPublicKey[wgtypes.Key(peer.PublicKey).String()]
|
publicKeyStr := wgtypes.Key(peer.PublicKey).String()
|
||||||
if !ok {
|
machineName := "(unknown)"
|
||||||
machineName = "(unknown)"
|
rtt := "-"
|
||||||
|
if m, ok := machinesByPublicKey[publicKeyStr]; ok {
|
||||||
|
machineName = m.Name
|
||||||
|
if selfMachine != nil {
|
||||||
|
if stats, ok := selfMachine.Rtts[m.Id]; ok {
|
||||||
|
rtt = tui.FormatRTT(stats.Median.AsDuration())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastHandshake := ""
|
lastHandshake := ""
|
||||||
@@ -113,20 +122,18 @@ func runShow(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
|||||||
lastHandshake = time.Since(peer.LastHandshakeTime.AsTime()).Round(time.Second).String() + " ago"
|
lastHandshake = time.Since(peer.LastHandshakeTime.AsTime()).Round(time.Second).String() + " ago"
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprintf(
|
t.Row(
|
||||||
tw,
|
|
||||||
"%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
|
||||||
machineName,
|
machineName,
|
||||||
wgtypes.Key(peer.PublicKey).String(),
|
publicKeyStr,
|
||||||
peer.Endpoint,
|
peer.Endpoint,
|
||||||
lastHandshake,
|
lastHandshake,
|
||||||
|
rtt,
|
||||||
units.HumanSize(float64(peer.ReceiveBytes)),
|
units.HumanSize(float64(peer.ReceiveBytes)),
|
||||||
units.HumanSize(float64(peer.TransmitBytes)),
|
units.HumanSize(float64(peer.TransmitBytes)),
|
||||||
strings.Join(peer.AllowedIps, ", "),
|
strings.Join(peer.AllowedIps, tui.Faint.Render(", ")),
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("write row: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return tw.Flush()
|
lipgloss.Println(t)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/internal/ucind"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewListCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Aliases: []string{"list"},
|
||||||
|
Short: "List clusters.",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
p := cmd.Context().Value("provisioner").(*ucind.Provisioner)
|
||||||
|
clusters, err := p.ListClusters(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("list clusters: %w", err)
|
||||||
|
}
|
||||||
|
t := tui.NewTable()
|
||||||
|
t.Headers("NAME", "MACHINES")
|
||||||
|
for _, cluster := range clusters {
|
||||||
|
machines := []string{}
|
||||||
|
for _, m := range cluster.Machines {
|
||||||
|
machines = append(machines, m.Name)
|
||||||
|
}
|
||||||
|
t.Row(
|
||||||
|
cluster.Name,
|
||||||
|
strings.Join(machines, tui.Faint.Render(", ")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fmt.Println(t)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/ucind"
|
"github.com/psviderski/uncloud/internal/ucind"
|
||||||
|
"github.com/psviderski/uncloud/internal/ucind/completion"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ func NewRemoveCommand() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "rm [NAME]",
|
Use: "rm [NAME]",
|
||||||
Short: "Remove a cluster.",
|
Short: "Remove a cluster.",
|
||||||
|
Aliases: []string{"remove", "delete"},
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
p := cmd.Context().Value("provisioner").(*ucind.Provisioner)
|
p := cmd.Context().Value("provisioner").(*ucind.Provisioner)
|
||||||
@@ -26,6 +28,12 @@ func NewRemoveCommand() *cobra.Command {
|
|||||||
fmt.Printf("Cluster '%s' removed.\n", name)
|
fmt.Printf("Cluster '%s' removed.\n", name)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||||
|
if len(args) > 0 {
|
||||||
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return completion.Clusters(cmd, args, toComplete)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ func NewRootCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
NewCreateCommand(),
|
NewCreateCommand(),
|
||||||
// NewListCommand(),
|
NewListCommand(),
|
||||||
NewRemoveCommand(),
|
NewRemoveCommand(),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
+4
-2
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/psviderski/uncloud/cmd/ucind/cluster"
|
"github.com/psviderski/uncloud/cmd/ucind/cluster"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/ucind"
|
"github.com/psviderski/uncloud/internal/ucind"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -23,6 +24,8 @@ func main() {
|
|||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
|
||||||
|
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create Docker client: %w", err)
|
return fmt.Errorf("create Docker client: %w", err)
|
||||||
@@ -44,9 +47,8 @@ func main() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: allow to override using UNCLOUD_CONFIG env var.
|
|
||||||
cmd.PersistentFlags().StringVar(&configPath, "uncloud-config", "~/.config/uncloud/config.yaml",
|
cmd.PersistentFlags().StringVar(&configPath, "uncloud-config", "~/.config/uncloud/config.yaml",
|
||||||
"path to the Uncloud configuration file.")
|
"Path to the Uncloud configuration file. [$UNCLOUD_CONFIG]")
|
||||||
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml")
|
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml")
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
|
|||||||
@@ -1,442 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mockDockerClient implements pb.DockerClient
|
|
||||||
type mockDockerClient struct {
|
|
||||||
pb.DockerClient // Embed to avoid implementing all methods
|
|
||||||
listResp *pb.ListServiceContainersResponse
|
|
||||||
listErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockDockerClient) ListServiceContainers(ctx context.Context, in *pb.ListServiceContainersRequest, opts ...grpc.CallOption) (*pb.ListServiceContainersResponse, error) {
|
|
||||||
return m.listResp, m.listErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// mockClusterClient implements pb.ClusterClient
|
|
||||||
type mockClusterClient struct {
|
|
||||||
pb.ClusterClient // Embed to avoid implementing all methods
|
|
||||||
machinesResp *pb.ListMachinesResponse
|
|
||||||
machinesErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mockClusterClient) ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*pb.ListMachinesResponse, error) {
|
|
||||||
return m.machinesResp, m.machinesErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCollectContainers_NilMetadata(t *testing.T) {
|
|
||||||
// Setup container data
|
|
||||||
containerData := map[string]any{
|
|
||||||
"Id": "container1",
|
|
||||||
"Name": "test-container",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "test-image",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON, err := json.Marshal(containerData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
serviceSpecJSON, err := json.Marshal(map[string]any{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Setup mocks
|
|
||||||
mockDocker := &mockDockerClient{
|
|
||||||
listResp: &pb.ListServiceContainersResponse{
|
|
||||||
Messages: []*pb.MachineServiceContainers{
|
|
||||||
{
|
|
||||||
Metadata: nil, // Simulating the issue: nil metadata
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
machineIP := "10.0.0.1"
|
|
||||||
mockCluster := &mockClusterClient{
|
|
||||||
machinesResp: &pb.ListMachinesResponse{
|
|
||||||
Machines: []*pb.MachineMember{
|
|
||||||
{
|
|
||||||
Machine: &pb.MachineInfo{
|
|
||||||
Name: "machine-1",
|
|
||||||
Network: &pb.NetworkConfig{
|
|
||||||
ManagementIp: pb.NewIP(netip.MustParseAddr(machineIP)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
State: pb.MachineMember_UP,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct client with mocks
|
|
||||||
cli := &client.Client{
|
|
||||||
Docker: &docker.Client{
|
|
||||||
GRPCClient: mockDocker,
|
|
||||||
},
|
|
||||||
ClusterClient: mockCluster,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
containers, err := collectContainers(context.Background(), cli)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
assert.Len(t, containers, 1)
|
|
||||||
if len(containers) > 0 {
|
|
||||||
c := containers[0]
|
|
||||||
assert.Equal(t, "container1", c.id)
|
|
||||||
assert.Equal(t, "test-container", c.name)
|
|
||||||
assert.Equal(t, "machine-1", c.machineName, "Should fall back to the single machine name when metadata is nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCollectContainers_NilMetadata_MultipleMachines_Error(t *testing.T) {
|
|
||||||
// If we have multiple machines but receive nil metadata, it should return an error as it is ambiguous
|
|
||||||
|
|
||||||
// Setup container data
|
|
||||||
containerData1 := map[string]any{
|
|
||||||
"Id": "container1",
|
|
||||||
}
|
|
||||||
containerJSON1, _ := json.Marshal(containerData1)
|
|
||||||
|
|
||||||
containerData2 := map[string]any{
|
|
||||||
"Id": "container2",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "test-image",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON2, _ := json.Marshal(containerData2)
|
|
||||||
|
|
||||||
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
|
||||||
|
|
||||||
// Setup mocks
|
|
||||||
mockDocker := &mockDockerClient{
|
|
||||||
listResp: &pb.ListServiceContainersResponse{
|
|
||||||
Messages: []*pb.MachineServiceContainers{
|
|
||||||
{
|
|
||||||
Metadata: nil, // Nil metadata
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON1,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: &pb.Metadata{Machine: "10.0.0.2"},
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON2,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mockCluster := &mockClusterClient{
|
|
||||||
machinesResp: &pb.ListMachinesResponse{
|
|
||||||
Machines: []*pb.MachineMember{
|
|
||||||
{
|
|
||||||
Machine: &pb.MachineInfo{
|
|
||||||
Name: "machine-1",
|
|
||||||
Network: &pb.NetworkConfig{
|
|
||||||
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.1")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Machine: &pb.MachineInfo{
|
|
||||||
Name: "machine-2",
|
|
||||||
Network: &pb.NetworkConfig{
|
|
||||||
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.2")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct client with mocks
|
|
||||||
cli := &client.Client{
|
|
||||||
Docker: &docker.Client{
|
|
||||||
GRPCClient: mockDocker,
|
|
||||||
},
|
|
||||||
ClusterClient: mockCluster,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
_, err := collectContainers(context.Background(), cli)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Contains(t, err.Error(), "metadata is missing for a machine response")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCollectContainers_MetadataPresent_MultipleMachines(t *testing.T) {
|
|
||||||
// Verify correct mapping of containers to machines when metadata is present
|
|
||||||
|
|
||||||
// Setup container data
|
|
||||||
containerData1 := map[string]any{
|
|
||||||
"Id": "container1",
|
|
||||||
"Name": "container-1",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "image-1",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON1, _ := json.Marshal(containerData1)
|
|
||||||
|
|
||||||
containerData2 := map[string]any{
|
|
||||||
"Id": "container2",
|
|
||||||
"Name": "container-2",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "image-2",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON2, _ := json.Marshal(containerData2)
|
|
||||||
|
|
||||||
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
|
||||||
|
|
||||||
// Setup mocks
|
|
||||||
mockDocker := &mockDockerClient{
|
|
||||||
listResp: &pb.ListServiceContainersResponse{
|
|
||||||
Messages: []*pb.MachineServiceContainers{
|
|
||||||
{
|
|
||||||
Metadata: &pb.Metadata{Machine: "10.0.0.1"},
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON1,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Metadata: &pb.Metadata{Machine: "10.0.0.2"},
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON2,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mockCluster := &mockClusterClient{
|
|
||||||
machinesResp: &pb.ListMachinesResponse{
|
|
||||||
Machines: []*pb.MachineMember{
|
|
||||||
{
|
|
||||||
Machine: &pb.MachineInfo{
|
|
||||||
Name: "machine-1",
|
|
||||||
Network: &pb.NetworkConfig{
|
|
||||||
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.1")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Machine: &pb.MachineInfo{
|
|
||||||
Name: "machine-2",
|
|
||||||
Network: &pb.NetworkConfig{
|
|
||||||
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.2")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cli := &client.Client{
|
|
||||||
Docker: &docker.Client{GRPCClient: mockDocker},
|
|
||||||
ClusterClient: mockCluster,
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := collectContainers(context.Background(), cli)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, containers, 2)
|
|
||||||
// Order is not guaranteed by the map iteration in logic or parallel fetch (though here it's mocked sequential),
|
|
||||||
// but collectContainers just appends.
|
|
||||||
// We'll find them by ID.
|
|
||||||
for _, c := range containers {
|
|
||||||
if c.id == "container1" {
|
|
||||||
assert.Equal(t, "machine-1", c.machineName)
|
|
||||||
} else if c.id == "container2" {
|
|
||||||
assert.Equal(t, "machine-2", c.machineName)
|
|
||||||
} else {
|
|
||||||
t.Errorf("unexpected container id: %s", c.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCollectContainers_NilMetadata_NoMachines(t *testing.T) {
|
|
||||||
// Case: 1 msc with nil metadata but no machines at all
|
|
||||||
|
|
||||||
containerData := map[string]any{
|
|
||||||
"Id": "container1",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "test-image",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON, _ := json.Marshal(containerData)
|
|
||||||
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
|
||||||
|
|
||||||
mockDocker := &mockDockerClient{
|
|
||||||
listResp: &pb.ListServiceContainersResponse{
|
|
||||||
Messages: []*pb.MachineServiceContainers{
|
|
||||||
{
|
|
||||||
Metadata: nil,
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// No machines in cluster response
|
|
||||||
mockCluster := &mockClusterClient{
|
|
||||||
machinesResp: &pb.ListMachinesResponse{
|
|
||||||
Machines: []*pb.MachineMember{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cli := &client.Client{
|
|
||||||
Docker: &docker.Client{GRPCClient: mockDocker},
|
|
||||||
ClusterClient: mockCluster,
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := collectContainers(context.Background(), cli)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, containers, 1)
|
|
||||||
if len(containers) > 0 {
|
|
||||||
assert.Equal(t, "unknown", containers[0].machineName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCollectContainers_MetadataPresent_NotInMapping(t *testing.T) {
|
|
||||||
// Case: msc with metadata that is not in the IP-to-name mapping
|
|
||||||
|
|
||||||
containerData := map[string]any{
|
|
||||||
"Id": "container1",
|
|
||||||
"Config": map[string]any{
|
|
||||||
"Image": "test-image",
|
|
||||||
},
|
|
||||||
"State": map[string]any{
|
|
||||||
"Status": "running",
|
|
||||||
"StartedAt": "2023-01-01T12:00:00Z",
|
|
||||||
"FinishedAt": "0001-01-01T00:00:00Z",
|
|
||||||
},
|
|
||||||
"NetworkSettings": map[string]any{
|
|
||||||
"Networks": map[string]any{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
containerJSON, _ := json.Marshal(containerData)
|
|
||||||
serviceSpecJSON, _ := json.Marshal(map[string]any{})
|
|
||||||
|
|
||||||
mockDocker := &mockDockerClient{
|
|
||||||
listResp: &pb.ListServiceContainersResponse{
|
|
||||||
Messages: []*pb.MachineServiceContainers{
|
|
||||||
{
|
|
||||||
Metadata: &pb.Metadata{Machine: "10.0.0.99"}, // Unknown IP
|
|
||||||
Containers: []*pb.ServiceContainer{
|
|
||||||
{
|
|
||||||
Container: containerJSON,
|
|
||||||
ServiceSpec: serviceSpecJSON,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mockCluster := &mockClusterClient{
|
|
||||||
machinesResp: &pb.ListMachinesResponse{
|
|
||||||
Machines: []*pb.MachineMember{
|
|
||||||
{
|
|
||||||
Machine: &pb.MachineInfo{
|
|
||||||
Name: "machine-1",
|
|
||||||
Network: &pb.NetworkConfig{
|
|
||||||
ManagementIp: pb.NewIP(netip.MustParseAddr("10.0.0.1")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cli := &client.Client{
|
|
||||||
Docker: &docker.Client{GRPCClient: mockDocker},
|
|
||||||
ClusterClient: mockCluster,
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := collectContainers(context.Background(), cli)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, containers, 1)
|
|
||||||
if len(containers) > 0 {
|
|
||||||
// Should fallback to the IP/string in metadata
|
|
||||||
assert.Equal(t, "10.0.0.99", containers[0].machineName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,363 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"charm.land/lipgloss/v2"
|
|
||||||
mapset "github.com/deckarep/golang-set/v2"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
|
||||||
"github.com/psviderski/uncloud/pkg/client/compose"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
type logsOptions struct {
|
|
||||||
files []string
|
|
||||||
follow bool
|
|
||||||
tail string
|
|
||||||
since string
|
|
||||||
until string
|
|
||||||
utc bool
|
|
||||||
machines []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogsCommand(groupID string) *cobra.Command {
|
|
||||||
var options logsOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "logs [SERVICE...]",
|
|
||||||
Aliases: []string{"log"},
|
|
||||||
Short: "View service logs.",
|
|
||||||
Long: `View logs from all replicas of the specified service(s) across all machines in the cluster.
|
|
||||||
|
|
||||||
If no services are specified, streams logs from all services defined in the Compose file
|
|
||||||
(compose.yaml by default or the file(s) specified with --file).`,
|
|
||||||
Example: ` # View recent logs for a service.
|
|
||||||
uc logs web
|
|
||||||
|
|
||||||
# Stream logs in real-time (follow mode).
|
|
||||||
uc logs -f web
|
|
||||||
|
|
||||||
# View logs from multiple services.
|
|
||||||
uc logs web api db
|
|
||||||
|
|
||||||
# View logs from all services in compose.yaml.
|
|
||||||
uc logs
|
|
||||||
|
|
||||||
# Show last 20 lines per replica (default is 100).
|
|
||||||
uc logs -n 20 web
|
|
||||||
|
|
||||||
# Show all logs without line limit.
|
|
||||||
uc logs -n all web
|
|
||||||
|
|
||||||
# View logs from a specific time range.
|
|
||||||
uc logs --since 3h --until 1h30m web
|
|
||||||
|
|
||||||
# View logs only from replicas running on specific machines.
|
|
||||||
uc logs -m machine1,machine2 web api`,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
|
||||||
return runLogs(cmd.Context(), uncli, args, options)
|
|
||||||
},
|
|
||||||
GroupID: groupID,
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().StringSliceVar(&options.files, "file", nil,
|
|
||||||
"One or more Compose files to load service names from when no services are specified. (default compose.yaml)")
|
|
||||||
cmd.Flags().BoolVarP(&options.follow, "follow", "f", false,
|
|
||||||
"Continually stream new logs.")
|
|
||||||
cmd.Flags().StringSliceVarP(&options.machines, "machine", "m", nil,
|
|
||||||
"Filter logs by machine name or ID. Can be specified multiple times or as a comma-separated list.")
|
|
||||||
cmd.Flags().StringVar(&options.since, "since", "",
|
|
||||||
"Show logs generated on or after the given timestamp. Accepts relative duration, RFC 3339 date, or Unix timestamp.\n"+
|
|
||||||
"Examples:\n"+
|
|
||||||
" --since 2m30s Relative duration (2 minutes 30 seconds ago)\n"+
|
|
||||||
" --since 1h Relative duration (1 hour ago)\n"+
|
|
||||||
" --since 2025-11-24 RFC 3339 date only (midnight using local timezone)\n"+
|
|
||||||
" --since 2024-05-14T22:50:00 RFC 3339 date/time using local timezone\n"+
|
|
||||||
" --since 2024-01-31T10:30:00Z RFC 3339 date/time in UTC\n"+
|
|
||||||
" --since 1763953966 Unix timestamp (seconds since January 1, 1970)")
|
|
||||||
cmd.Flags().StringVarP(&options.tail, "tail", "n", "100",
|
|
||||||
"Show the most recent logs and limit the number of lines shown per replica. Use 'all' to show all logs.")
|
|
||||||
cmd.Flags().StringVar(&options.until, "until", "",
|
|
||||||
"Show logs generated before the given timestamp. Accepts relative duration, RFC 3339 date, or Unix timestamp.\n"+
|
|
||||||
"See --since for examples.")
|
|
||||||
cmd.Flags().BoolVar(&options.utc, "utc", false,
|
|
||||||
"Print timestamps in UTC instead of local timezone.")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLogs(ctx context.Context, uncli *cli.CLI, serviceNames []string, opts logsOptions) error {
|
|
||||||
// If no services specified, try to load them from the Compose file(s).
|
|
||||||
fromCompose := false
|
|
||||||
if len(serviceNames) == 0 {
|
|
||||||
fromCompose = true
|
|
||||||
project, err := compose.LoadProject(ctx, opts.files)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("load Compose file(s): %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
uncli.SetClusterContextIfUnset(compose.ClusterContext(project))
|
|
||||||
|
|
||||||
// View logs for all services, including disabled by inactive profiles.
|
|
||||||
serviceNames = append(project.ServiceNames(), project.DisabledServiceNames()...)
|
|
||||||
if len(serviceNames) == 0 {
|
|
||||||
return errors.New("no services found in Compose file(s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse tail option.
|
|
||||||
tail := -1
|
|
||||||
if opts.tail != "all" {
|
|
||||||
tailInt, err := strconv.Atoi(opts.tail)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid --tail value '%s': %w", opts.tail, err)
|
|
||||||
}
|
|
||||||
tail = tailInt
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := uncli.ConnectCluster(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
logsOpts := api.ServiceLogsOptions{
|
|
||||||
Follow: opts.follow,
|
|
||||||
Tail: tail,
|
|
||||||
Since: opts.since,
|
|
||||||
Until: opts.until,
|
|
||||||
Machines: cli.ExpandCommaSeparatedValues(opts.machines),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect log streams from all services. When service names come from a Compose file,
|
|
||||||
// skip the ones that are not found in the cluster (they may have been removed or not deployed yet).
|
|
||||||
machineIDsSet := mapset.NewSet[string]()
|
|
||||||
svcStreams := make([]<-chan api.ServiceLogEntry, 0, len(serviceNames))
|
|
||||||
var foundServices, notFoundServices []string
|
|
||||||
|
|
||||||
for _, serviceName := range serviceNames {
|
|
||||||
svc, ch, err := c.ServiceLogs(ctx, serviceName, logsOpts)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, api.ErrNotFound) && fromCompose {
|
|
||||||
notFoundServices = append(notFoundServices, serviceName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Errorf("stream logs for service '%s': %w", serviceName, err)
|
|
||||||
}
|
|
||||||
svcStreams = append(svcStreams, ch)
|
|
||||||
foundServices = append(foundServices, serviceName)
|
|
||||||
|
|
||||||
machineIDs := svc.MachineIDs()
|
|
||||||
machineIDsSet.Append(machineIDs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fromCompose {
|
|
||||||
if len(foundServices) == 0 {
|
|
||||||
return fmt.Errorf("stream logs for services defined in %s: no services found in the cluster",
|
|
||||||
strings.Join(opts.files, ", "))
|
|
||||||
}
|
|
||||||
serviceNames = foundServices
|
|
||||||
|
|
||||||
for _, name := range notFoundServices {
|
|
||||||
tui.PrintWarning(fmt.Sprintf("service '%s' not found in the cluster, skipping", name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var stream <-chan api.ServiceLogEntry
|
|
||||||
if len(serviceNames) == 1 {
|
|
||||||
stream = svcStreams[0]
|
|
||||||
} else {
|
|
||||||
// Merge all service streams into a single sorted stream without stall detection as its handled per-service.
|
|
||||||
merger := client.NewLogMerger(svcStreams, client.LogMergerOptions{})
|
|
||||||
stream = merger.Stream()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch machine names for all machines (machineIDsSet) service containers are running on.
|
|
||||||
machines, err := c.ListMachines(ctx, &api.MachineFilter{NamesOrIDs: machineIDsSet.ToSlice()})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("list machines: %w", err)
|
|
||||||
}
|
|
||||||
machineNames := make([]string, 0, len(machines))
|
|
||||||
for _, m := range machines {
|
|
||||||
machineNames = append(machineNames, m.Machine.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatter := newLogFormatter(machineNames, serviceNames, opts.utc)
|
|
||||||
|
|
||||||
// Print merged logs.
|
|
||||||
for entry := range stream {
|
|
||||||
if entry.Err != nil {
|
|
||||||
formatter.printError(entry)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
formatter.printEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Available colors for machine/service differentiation.
|
|
||||||
var colorPalette = []color.Color{
|
|
||||||
lipgloss.BrightGreen,
|
|
||||||
lipgloss.BrightYellow,
|
|
||||||
lipgloss.BrightBlue,
|
|
||||||
lipgloss.BrightMagenta,
|
|
||||||
lipgloss.BrightCyan,
|
|
||||||
lipgloss.Green,
|
|
||||||
lipgloss.Yellow,
|
|
||||||
lipgloss.Blue,
|
|
||||||
lipgloss.Magenta,
|
|
||||||
lipgloss.Cyan,
|
|
||||||
}
|
|
||||||
|
|
||||||
// logFormatter handles formatting and printing of log entries with dynamic column alignment.
|
|
||||||
type logFormatter struct {
|
|
||||||
machineNames []string
|
|
||||||
serviceNames []string
|
|
||||||
|
|
||||||
maxMachineWidth int
|
|
||||||
maxServiceWidth int
|
|
||||||
|
|
||||||
utc bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLogFormatter(machineNames, serviceNames []string, utc bool) *logFormatter {
|
|
||||||
slices.Sort(machineNames)
|
|
||||||
slices.Sort(serviceNames)
|
|
||||||
|
|
||||||
maxMachineWidth := 0
|
|
||||||
for _, name := range machineNames {
|
|
||||||
if len(name) > maxMachineWidth {
|
|
||||||
maxMachineWidth = len(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
maxServiceWidth := 0
|
|
||||||
for _, name := range serviceNames {
|
|
||||||
if len(name) > maxServiceWidth {
|
|
||||||
maxServiceWidth = len(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &logFormatter{
|
|
||||||
machineNames: machineNames,
|
|
||||||
serviceNames: serviceNames,
|
|
||||||
maxMachineWidth: maxMachineWidth,
|
|
||||||
maxServiceWidth: maxServiceWidth,
|
|
||||||
utc: utc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatTimestamp formats timestamp using local timezone or UTC if configured.
|
|
||||||
func (f *logFormatter) formatTimestamp(t time.Time) string {
|
|
||||||
if f.utc {
|
|
||||||
t = t.UTC()
|
|
||||||
} else {
|
|
||||||
t = t.In(time.Local)
|
|
||||||
}
|
|
||||||
dimStyle := lipgloss.NewStyle().Faint(true)
|
|
||||||
|
|
||||||
return dimStyle.Render(t.Format(time.StampMilli))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *logFormatter) formatMachine(name string) string {
|
|
||||||
style := lipgloss.NewStyle().Bold(true).PaddingRight(f.maxMachineWidth - len(name))
|
|
||||||
|
|
||||||
if len(f.serviceNames) == 1 {
|
|
||||||
// Machine name is coloured for single-service logs.
|
|
||||||
i := slices.Index(f.machineNames, name)
|
|
||||||
if i == -1 {
|
|
||||||
f.machineNames = append(f.machineNames, name)
|
|
||||||
i = len(f.machineNames) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
style = style.Foreground(colorPalette[i%len(colorPalette)])
|
|
||||||
}
|
|
||||||
|
|
||||||
return style.Render(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *logFormatter) formatServiceContainer(serviceName, containerID string) string {
|
|
||||||
styleService := lipgloss.NewStyle().Bold(true).PaddingRight(f.maxServiceWidth - len(serviceName))
|
|
||||||
styleContainer := lipgloss.NewStyle().Faint(true)
|
|
||||||
|
|
||||||
if len(f.serviceNames) > 1 {
|
|
||||||
// Service name is coloured for multi-service logs.
|
|
||||||
i := slices.Index(f.serviceNames, serviceName)
|
|
||||||
if i == -1 {
|
|
||||||
f.serviceNames = append(f.serviceNames, serviceName)
|
|
||||||
i = len(f.serviceNames) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
styleService = styleService.Foreground(colorPalette[i%len(colorPalette)])
|
|
||||||
}
|
|
||||||
|
|
||||||
return styleService.Render(serviceName) + styleContainer.Render("["+containerID[:5]+"]")
|
|
||||||
}
|
|
||||||
|
|
||||||
// printEntry prints a single log entry with proper formatting.
|
|
||||||
func (f *logFormatter) printEntry(entry api.ServiceLogEntry) {
|
|
||||||
if entry.Stream != api.LogStreamStdout && entry.Stream != api.LogStreamStderr {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var output strings.Builder
|
|
||||||
|
|
||||||
// Timestamp
|
|
||||||
output.WriteString(f.formatTimestamp(entry.Timestamp))
|
|
||||||
output.WriteString(" ")
|
|
||||||
|
|
||||||
// Machine name
|
|
||||||
output.WriteString(f.formatMachine(entry.Metadata.MachineName))
|
|
||||||
output.WriteString(" ")
|
|
||||||
|
|
||||||
// Service[container_id]
|
|
||||||
output.WriteString(f.formatServiceContainer(entry.Metadata.ServiceName, entry.Metadata.ContainerID))
|
|
||||||
output.WriteString(" ")
|
|
||||||
|
|
||||||
// Message
|
|
||||||
output.Write(entry.Message)
|
|
||||||
|
|
||||||
// Print to appropriate stream.
|
|
||||||
if entry.Stream == api.LogStreamStderr {
|
|
||||||
fmt.Fprint(os.Stderr, output.String())
|
|
||||||
} else {
|
|
||||||
fmt.Print(output.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printError prints an error entry (e.g., stalled stream warning).
|
|
||||||
func (f *logFormatter) printError(entry api.ServiceLogEntry) {
|
|
||||||
if entry.Metadata.ContainerID != "" {
|
|
||||||
msg := fmt.Sprintf("WARNING: log stream from %s[%s] on machine '%s'",
|
|
||||||
entry.Metadata.ServiceName,
|
|
||||||
stringid.TruncateID(entry.Metadata.ContainerID),
|
|
||||||
entry.Metadata.MachineName)
|
|
||||||
|
|
||||||
if errors.Is(entry.Err, api.ErrLogStreamStalled) {
|
|
||||||
msg += " stopped responding"
|
|
||||||
} else {
|
|
||||||
msg += fmt.Sprintf(": %v", entry.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
style := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("11")) // Bold bright yellow
|
|
||||||
fmt.Fprintln(os.Stderr, style.Render(msg))
|
|
||||||
} else {
|
|
||||||
msg := fmt.Sprintf("ERROR: %v", entry.Err)
|
|
||||||
style := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("9")) // Bold bright red
|
|
||||||
fmt.Fprintln(os.Stderr, style.Render(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
_ _ _
|
||||||
|
_ _ _ __ ___| | ___ _ _ __| | __| |
|
||||||
|
| | | | '_ \ / __| |/ _ \| | | |/ _` |/ _` |
|
||||||
|
| |_| | | | | (__| | (_) | |_| | (_| | (_| |
|
||||||
|
\__,_|_| |_|\___|_|\___/ \__,_|\__,_|\__,_|
|
||||||
@@ -23,7 +23,7 @@ func newDialStdioCommand() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultUncloudSockPath,
|
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultClusterAPISockPath,
|
||||||
"Path to the Uncloud API socket")
|
"Path to the Uncloud API socket")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
Loaded 100 of 433 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user