Compare commits

..
266 changed files with 21094 additions and 32220 deletions
-20
View File
@@ -1,20 +0,0 @@
# EditorConfig is awesome: https://editorconfig.org
root = true
# Default settings for all files
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Settings for Go files
[*.go]
indent_style = tab
indent_size = 4
# Settings for Bash scripts
[*.sh]
indent_style = space
indent_size = 4
-1
View File
@@ -1 +0,0 @@
github: [psviderski]
-36
View File
@@ -1,36 +0,0 @@
---
name: Bug Report
about: Create a report to help us improve
title: "[BUG] "
labels: bug
assignees: ""
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**How to reproduce**
<!-- Steps to reproduce the behavior:
1. Run ...
2. Do ...
-->
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Environment:**
- Uncloud versions:
- Control (client) node (`uc --version`):
- Uncloud daemon (from the server) (`uncloudd --version`):
- OS version (`uname -a`):
- Client (control node):
- Server:
**Additional context**
<!-- Add any other context about the problem here. -->
-1
View File
@@ -1 +0,0 @@
../AI.md
Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 KiB

-69
View File
@@ -1,69 +0,0 @@
name: Go Tests
on:
push:
branches:
- "main"
- "test/**"
- "release/**"
pull_request:
branches:
- main
paths:
- ".github/**"
- "**.go"
- "**.proto"
- "go.*"
- "Makefile"
- "scripts/**"
- "test/**"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.23.2"
- name: Install dependencies
run: |
go mod tidy
git diff --exit-code ||
(echo "go.mod or go.sum has changed. Please run 'go mod tidy' and commit the changes." && exit 1)
- name: Run tests
run: |
make ucind-image
make test
timeout-minutes: 10
check-protobuf:
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
matrix:
# Run on more platforms so that mise lockfile checksums are checked/generated for all platforms.
# See https://github.com/jdx/mise/issues/4276 for details.
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Mise
uses: jdx/mise-action@13abe502c30c1559a5c37dff303831bab82c9402 # v2.2.3
with:
version: "2025.6.5"
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Generate Protobuf files
run: |
make proto
# check if the generated files are up to date
git diff --exit-code ||
(echo "Generated protobuf files are not up to date. Please run 'make proto' and commit the changes." && exit 1)
-39
View File
@@ -1,39 +0,0 @@
name: Lint
on:
push:
branches:
- "main"
- "test/**"
- "release/**"
pull_request:
branches:
- main
paths:
- "**.go"
- "go.*"
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: "1.23.2"
- name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.2.2
- name: Format code
run: |
make format
git diff --exit-code ||
(echo "Code is not formatted. Please run 'make format' and commit the changes." && exit 1)
timeout-minutes: 10
-1
View File
@@ -30,7 +30,6 @@ go.work.sum
/data/ /data/
/dist/ /dist/
/uc /uc
/tmp
# Web # Web
node_modules/ node_modules/
-42
View File
@@ -1,42 +0,0 @@
version: "2"
run:
concurrency: 4
tests: true
timeout: 5m
linters:
default: none
enable:
- bodyclose
# - dogsled
- dupl
# - errcheck
- gochecknoinits
- goconst
# - gocritic
# - gocyclo
# - godot
# - gosec
# - govet
- ineffassign
- misspell
- nakedret
# - prealloc
# - revive
# - staticcheck
- unconvert
# - unparam
# - unused
- whitespace
exclusions:
rules:
- path: ^test/e2e
linters:
- goconst # constants here add no value, so we skip goconst only for test/e2e.
formatters:
enable:
- gofumpt
- goimports
-47
View File
@@ -1,47 +0,0 @@
[tools."aqua:vektra/mockery"]
version = "3.5.3"
backend = "aqua:vektra/mockery"
[tools."aqua:vektra/mockery".checksums]
"mockery_3.5.3_Darwin_arm64.tar.gz" = "sha256:a3a94b14c7414e148f2252199ffc4a0108d311358f3d336cbe05bb73cb203704"
"mockery_3.5.3_Linux_x86_64.tar.gz" = "sha256:ebce416b0175338525246c376885a1579ca6cd4d4015140ba0c70e6b5339a39c"
[tools.go]
version = "1.23.10"
backend = "core:go"
[tools.go.checksums]
"go1.23.10.darwin-arm64.tar.gz" = "sha256:25c64bfa8a8fd8e7f62fb54afa4354af8409a4bb2358c2699a1003b733e6fce5"
"go1.23.10.linux-amd64.tar.gz" = "sha256:535f9f81802499f2a7dbfa70abb8fda3793725fcc29460f719815f6e10b5fd60"
[tools.golangci-lint]
version = "2.2.2"
backend = "aqua:golangci/golangci-lint"
[tools.golangci-lint.checksums]
"golangci-lint-2.2.2-darwin-arm64.tar.gz" = "sha256:d84d94d042c0d495fd1746f3d18948a75de163b17a14e8de3ef840928dd2df74"
"golangci-lint-2.2.2-linux-amd64.tar.gz" = "sha256:c27fbde948a87d326feacd21df2f61a9c54dbd2e3bfa185c0a1cd6917a6f964f"
[tools.protoc]
version = "27.3"
backend = "aqua:protocolbuffers/protobuf/protoc"
[tools.protoc.checksums]
"protoc-27.3-linux-x86_64.zip" = "sha256:6dab2adab83f915126cab53540d48957c40e9e9023969c3e84d44bfb936c7741"
"protoc-27.3-osx-aarch_64.zip" = "sha256:b22116bd97cdbd7ea25346abe635a9df268515fe5ef5afa93cd9a68fc2513f84"
[tools.protoc-gen-go]
version = "1.34.2"
backend = "aqua:protocolbuffers/protobuf-go/protoc-gen-go"
[tools.protoc-gen-go.checksums]
"protoc-gen-go.v1.34.2.darwin.arm64.tar.gz" = "sha256:17aca7f948dbb624049030cf841e35895cf34183ba006e721247fdeb95ff2780"
"protoc-gen-go.v1.34.2.linux.amd64.tar.gz" = "sha256:b87bc134dee55576a842141bf0ed27761c635d746780fce5dee038c6dd16554f"
[tools.protoc-gen-go-grpc]
version = "1.5.1"
backend = "aqua:grpc/grpc-go/protoc-gen-go-grpc"
[tools.protoc-gen-go-grpc.checksums]
"protoc-gen-go-grpc.v1.5.1.darwin.arm64.tar.gz" = "sha256:d6083feb51dcfe59f26793e99ef01ef5eac68b64294ec2546711f614ac5878f3"
"protoc-gen-go-grpc.v1.5.1.linux.amd64.tar.gz" = "sha256:a6cac4ea731e54aea304ad44d704a69d1cdc82997084b33637e21a89dc9229d6"
-8
View File
@@ -1,13 +1,5 @@
[settings]
experimental = true
[tools] [tools]
"aqua:vektra/mockery" = "3.5.3"
go = "1.23" go = "1.23"
golangci-lint = "2.2.2"
protoc = "27.3"
protoc-gen-go = "1.34.2"
protoc-gen-go-grpc = "1.5.1"
[env] [env]
_.file = ".env" _.file = ".env"
-4
View File
@@ -1,4 +0,0 @@
packages:
github.com/psviderski/uncloud/internal/machine/caddyconfig:
interfaces:
CaddyfileValidator:
-244
View File
@@ -1,244 +0,0 @@
# AI.md - Uncloud Project Guide
This document provides comprehensive information about the Uncloud project for AI assistants to understand the codebase, architecture, and development practices.
## Project Overview
**Uncloud** is a lightweight clustering and container orchestration tool that enables deployment and management of web applications across cloud VMs and bare metal servers. It creates a secure WireGuard mesh network between Docker hosts and provides automatic service discovery, load balancing, HTTPS ingress, and simple CLI commands for application management.
### Key Characteristics
- **Language**: Go
- **Architecture**: Decentralized, no control plane
- **Target**: Self-hosted infrastructure without Kubernetes complexity
- **License**: View LICENSE file for details
- **Status**: Active development, not yet ready for production
## Core Features
### 🏗️ Infrastructure
- **Multi-machine deployment**: Combine cloud VMs, dedicated servers, and bare metal
- **Zero-config networking**: Automatic WireGuard mesh with NAT traversal
- **Decentralized design**: No central control plane, all machines are equal
- **Service discovery**: Built-in DNS server resolves service names to container IPs
### 🚀 Application Management
- **Docker Compose compatibility**: Uses familiar Docker Compose format
- **Zero-downtime deployments**: Rolling updates without service interruption
- **Automatic HTTPS**: Caddy reverse proxy with Let's Encrypt integration
- **Managed DNS**: Free `*.cluster.uncloud.run` subdomains via Uncloud DNS service
- **Cross-machine scaling**: Run containers across multiple machines
### 🔧 Developer Experience
- **Docker-like CLI**: Familiar commands (`uc` binary)
- **Imperative operations**: Direct commands vs. declarative state reconciliation
- **Remote management**: Control entire infrastructure via SSH to any machine
- **Minimal overhead**: ~150MB RAM footprint per machine
## Architecture
### Core Components
1. **CLI (`uc`)** - Main user interface for cluster management
2. **Daemon (`uncloudd`)** - Machine daemon running on each node
3. **Corrosion** - Distributed SQLite database for cluster state (Fly.io project)
4. **Caddy** - Reverse proxy for HTTPS termination and routing
5. **WireGuard** - Secure mesh networking between machines
### Network Architecture
- Each machine gets unique subnet (e.g., `10.210.0.0/24`, `10.210.1.0/24`)
- Containers get cluster-unique IPs for direct communication
- Automatic peer discovery and key management
- NAT traversal for machines behind firewalls
### State Management
- **CRDT-based distributed storage** using Corrosion
- **Eventually consistent** state across all machines
- **Gossip protocol** (Serf) for state propagation
- **No quorum requirements** - partial network splits remain functional
## Project Structure
### Key Directories
- **`cmd/`**: Contains main applications
- `uncloud/`: CLI tool with subcommands for machine, service, volume management
- `uncloudd/`: Daemon that runs on each machine
- `ucind/`: Development cluster management for testing
- **`internal/`**: Internal implementation packages
- `cli/`: Command-line interface logic
- `machine/`: Machine lifecycle and state management
- `daemon/`: Daemon implementation and gRPC services
- `dns/`: Internal DNS server for service discovery
- **`pkg/`**: Public API packages for external use
- `api/`: Core API types and definitions
- `client/`: Client libraries for interacting with Uncloud
- **`experiment/`**: Experimental features and prototypes
- **`scripts/`**: Installation and utility scripts
- **`test/`**: Test suites and test infrastructure
- **`website/`**: Documentation website (Docusaurus)
- **`misc/`**: Design documents and guides
## Key Technologies
### Core Dependencies
```go
// Networking and orchestration
github.com/docker/docker // Docker API client
github.com/docker/compose/v2 // Docker Compose integration
golang.zx2c4.com/wireguard // WireGuard implementation
github.com/hashicorp/serf // Gossip protocol
// State management
github.com/ipfs/go-ds-crdt // CRDT distributed storage
github.com/dgraph-io/badger/v3 // Embedded database
// Web proxy
github.com/caddyserver/caddy/v2 // HTTP server and reverse proxy
// CLI and UX
github.com/spf13/cobra // CLI framework
github.com/charmbracelet/huh // Interactive forms
// gRPC and networking
google.golang.org/grpc // gRPC framework
github.com/siderolabs/grpc-proxy // gRPC proxy for forwarding
```
## Development Workflow
### Build and Development
```bash
# Build binaries
go build -o uncloud ./cmd/uncloud
go build -o uncloudd ./cmd/uncloudd
```
### Key Make Targets
- `proto`: Generate protobuf code
- `ucind-cluster`: Create development cluster
- `update-dev`: Deploy to development machines
- `demo-reset`: Reset demo environment
- `fmt`: Format code
- `test`: Run all tests
- `lint`: Lint the code using golangci-lint
- `lint-and-fix`: Lint the code and fix issues whenever possible
## CLI Commands Structure
The `uc` CLI provides these main command groups:
### Machine Management
```bash
uc machine init <user@host> # Initialize new cluster
uc machine add <user@host> # Add machine to cluster
uc machine ls # List machines
uc machine rm <name> # Remove machine
```
### Service Management
```bash
uc run <image> # Run container from image
uc deploy # Deploy from compose.yaml
uc scale <service> <count> # Scale service replicas
uc ls # List services
uc rm <service> # Remove service
```
### Context and Connectivity
```bash
uc context ls # List available contexts
uc context use <name> # Switch context
```
### Global Flags
- `--connect`: Connect to remote machine directly, without a config file
- `--uncloud-config`: Override config file path
## Development Guidelines
### Code Organization
- **Package naming**: Use clear, descriptive names
- **Error handling**: Wrap errors with context using `fmt.Errorf`
- **Logging**: Use structured logging with levels
- **gRPC**: Services defined in `internal/machine/api/pb/`
### Testing
- Unit tests alongside source files (`*_test.go`)
- Integration tests in `test/e2e/`
- Test fixtures in `test/fixtures/`
### Dependencies
- Prefer standard library when possible
- Pin versions in `go.mod`
- Document rationale for external dependencies
### Configuration
- Support environment variables for key settings
- Validate configuration early
- Provide sensible defaults
## Troubleshooting and Debugging
### Common Issues
- **Networking**: Check WireGuard status, iptables rules
- **DNS**: Verify service discovery resolution
- **Containers**: Use standard Docker debugging tools
- **State sync**: Check Corrosion logs for replication issues
### Debugging Tools
- Standard Linux networking tools (`ping`, `traceroute`, `wireshark`)
- Docker commands (`docker ps`, `docker logs`)
- SSH access to machines for direct inspection
- gRPC debugging tools
### Logs and Monitoring
- Systemd services (getting logs via `journalctl -u SERVICE_NAME`)
- `uncloud` -- Uncloud daemon
- `uncloud-corrosion` -- Corrosion process
- Machine daemon logs
- Container logs via Docker
## File Patterns and Conventions
### Important Files to Understand
- `cmd/uncloud/main.go`: CLI entry point and command structure
- `internal/cli/cli.go`: CLI implementation and configuration
- `internal/machine/machine.go`: Core machine management
- `pkg/api/`: Public API definitions
- `misc/design.md`: Architecture and design philosophy
- `README.md`: User-facing documentation
### Configuration Files
- `go.mod/go.sum`: Go dependency management
- `Makefile`: Build and development tasks
- `Dockerfile`: Container build instructions forUncloud-in-Docker (used for testing)
This document should help AI assistants understand the project structure, make informed suggestions, and contribute effectively to the Uncloud codebase.
-1
View File
@@ -1 +0,0 @@
./AI.md
+3 -12
View File
@@ -17,22 +17,13 @@ RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o uncloudd cmd/uncloudd/main
FROM alpine:${ALPINE_VERSION} AS corrosion-download FROM alpine:${ALPINE_VERSION} AS corrosion-download
ARG TARGETARCH RUN wget -q -O /tmp/corrosion.tar.gz \
https://github.com/psviderski/corrosion/releases/latest/download/corrosion-aarch64-unknown-linux-gnu.tar.gz \
RUN CORROSION_ARCH=$(case "${TARGETARCH}" in \
"amd64") echo "x86_64" ;; \
"arm64") echo "aarch64" ;; \
*) echo "Architecture '${TARGETARCH}' not supported" >&2 && exit 1 ;; \
esac) \
&& wget -q -O /tmp/corrosion.tar.gz \
"https://github.com/psviderski/corrosion/releases/latest/download/corrosion-${CORROSION_ARCH}-unknown-linux-gnu.tar.gz" \
&& 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/local/bin/corrosion \
&& rm /tmp/corrosion.tar.gz /tmp/corrosion && rm /tmp/corrosion.tar.gz /tmp/corrosion
# Beware that more modern images like chainguard/wolfi-base build glibc with flags that require newer CPU features, FROM chainguard/wolfi-base:latest AS corrosion
# e.g. x86-64-v2. So such image may fail with "Fatal glibc error: CPU does not support x86-64-v2" on older CPUs.
FROM gcr.io/distroless/cc-debian12:latest AS corrosion
COPY --from=corrosion-download /usr/local/bin/corrosion /usr/local/bin/corrosion COPY --from=corrosion-download /usr/local/bin/corrosion /usr/local/bin/corrosion
CMD ["corrosion", "agent"] CMD ["corrosion", "agent"]
+6 -64
View File
@@ -45,76 +45,18 @@ proto:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative \ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative \
--proto_path=. --proto_path=internal/machine/api/vendor internal/machine/api/pb/*.proto --proto_path=. --proto_path=internal/machine/api/vendor internal/machine/api/pb/*.proto
.PHONY: proto-mise
proto-mise:
mise exec -- protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative \
--proto_path=. --proto_path=internal/machine/api/vendor internal/machine/api/pb/*.proto
.PHONY: corrosion-image .PHONY: corrosion-image
corrosion-image: corrosion-image:
docker build -t "$(CORROSION_IMAGE)" --target corrosion . docker build -t "$(CORROSION_IMAGE)" --target corrosion .
.PHONY: corrosion-multiarch-image-push .PHONY: corrosion-image-push
corrosion-multiarch-image-push: corrosion-image-push: corrosion-image
docker buildx build --push --platform linux/amd64,linux/arm64 -t "$(CORROSION_IMAGE)" --target corrosion . docker push "$(CORROSION_IMAGE)"
.PHONY: ucind-image .PHONY: ucind-image
ucind-image: ucind-image:
docker build -t "$(UCIND_IMAGE)" --target ucind . docker build -t "$(UCIND_IMAGE)" --target ucind .
.PHONY: ucind-multiarch-image-push .PHONY: ucind-image-push
ucind-multiarch-image-push: ucind-image-push: ucind-image
docker buildx build --push --platform linux/amd64,linux/arm64 -t "$(UCIND_IMAGE)" --target ucind . docker push "$(UCIND_IMAGE)"
.PHONY: mocks
mocks:
@mockery
.PHONY: test
test:
ifeq ($(TEST_NAME),)
go test -count=1 -v ./...
else
go test -race -count=1 -v -run ^$(TEST_NAME)$$ ./...
endif
.PHONY: test-e2e
test-e2e:
go test -race -count=1 -v ./test/e2e
.PHONY: test-clean
test-clean:
@CONTAINERS=$$(docker ps --filter "name=ucind-test" -q); \
if [ -n "$$CONTAINERS" ]; then \
echo "Killing containers..."; \
docker kill $$CONTAINERS; \
fi; \
CONTAINERS_STOPPED=$$(docker ps -a --filter "name=ucind-test" -q); \
if [ -n "$$CONTAINERS_STOPPED" ]; then \
echo "Removing stopped containers..."; \
docker rm $$CONTAINERS_STOPPED; \
else \
echo "Nothing to clean."; \
fi
.PHONY: vet
vet:
go vet ./...
.PHONY: format fmt
format fmt:
GOOS=linux golangci-lint fmt
LINT_TARGETS := lint lint-and-fix
.PHONY: $(LINT_TARGETS) _lint
$(LINT_TARGETS): _lint
lint: ARGS=
lint-and-fix: ARGS=--fix
_lint:
# Explicitly set OS to Linux to not skip *_linux.go files when running on macOS.
# Uncloud daemon won't likely support OS other than Linux anytime soon, so for now we can rely on that.
GOOS=linux golangci-lint run $(ARGS)
.PHONY: cli-docs
cli-docs:
go run ./cmd/uncloud docs
+7 -60
View File
@@ -1,13 +1,11 @@
<div align="center"> <div align="center">
<img src="./website/landing/images/logo.svg" height="100" width="100" alt="Uncloud logo"/> <img src="./website/images/logo.svg" height="100" alt="Uncloud logo"/>
<h1>Uncloud</h1> <h1>Uncloud</h1>
<p><strong>Docker simplicity. Multi-machine power.</strong></p> <p><strong>Docker simplicity. Multi-machine power.</strong></p>
<p> <p>
<a href="https://uncloud.run/docs"><img src="https://img.shields.io/badge/Docs-blue.svg?style=for-the-badge&logo=gitbook&logoColor=white" alt="Documentation"></a>
<a href="https://discord.gg/eR35KQJhPu"><img src="https://img.shields.io/badge/discord-5865F2.svg?style=for-the-badge&logo=discord&logoColor=white" alt="Join Discord"></a> <a href="https://discord.gg/eR35KQJhPu"><img src="https://img.shields.io/badge/discord-5865F2.svg?style=for-the-badge&logo=discord&logoColor=white" alt="Join Discord"></a>
<a href="https://x.com/psviderski"><img src="https://img.shields.io/badge/follow-black?style=for-the-badge&logo=X&logoColor=while" alt="Follow on X"></a> <a href="https://x.com/psviderski"><img src="https://img.shields.io/badge/follow-black?style=for-the-badge&logo=X&logoColor=while" alt="Follow on X"></a>
<a href="https://github.com/sponsors/psviderski"><img src="https://img.shields.io/badge/Donate-EA4AAA.svg?style=for-the-badge&logo=githubsponsors&logoColor=white" alt="Donate"></a>
</p> </p>
</div> </div>
@@ -45,23 +43,9 @@ complexity of Kubernetes.
* **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.
### 🚀 Coming soon
* **[Unregistry](https://github.com/psviderski/unregistry) integration**: Push your Docker images directly to your
machines without an external registry. It will transfer only the missing layers, making it fast and efficient.
## 🎬 Quick demo ## 🎬 Quick demo
The screenshot below demonstrates how I use Uncloud to deploy https://uncloud.run website to 2 remote machines from Deploy a highly available web app with automatic HTTPS across multiple regions and on-premises in just a couple minutes.
the [`compose.yaml`](website/compose.yaml) file on my local machine.
It exposes the container port `8000/tcp` as HTTPS on the domain `uncloud.run`, served by the Caddy reverse proxy on the
remote machines. All managed by Uncloud.
![Uncloud compose deployment demo](.github/images/compose-deploy.jpg)
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.
<a href="https://uncloud.wistia.com/medias/k47uwt9uau?wvideo=k47uwt9uau"> <a href="https://uncloud.wistia.com/medias/k47uwt9uau?wvideo=k47uwt9uau">
<img src="https://embed-ssl.wistia.com/deliveries/3cf7014a48b93afc556444bed3e39a8c.jpg?image_crop_resized=900x526&image_play_button_rounded=true&image_play_button_size=2x&image_play_button_color=18181Be0" alt="Uncloud demo" width="450" height="263" /> <img src="https://embed-ssl.wistia.com/deliveries/3cf7014a48b93afc556444bed3e39a8c.jpg?image_crop_resized=900x526&image_play_button_rounded=true&image_play_button_size=2x&image_play_button_color=18181Be0" alt="Uncloud demo" width="450" height="263" />
@@ -97,8 +81,6 @@ platform, whether you're running on a $5 VPS, a spare Mac mini, or a rack of bar
curl -fsS https://get.uncloud.run/install.sh | sh curl -fsS https://get.uncloud.run/install.sh | sh
``` ```
See [Installation](https://uncloud.run/docs/getting-started/install-cli) for more options.
2. Initialise your first machine: 2. Initialise your first machine:
```bash ```bash
@@ -130,15 +112,15 @@ platform, whether you're running on a $5 VPS, a spare Mac mini, or a rack of bar
uncloud-uninstall uncloud-uninstall
``` ```
View the [Documentation](https://uncloud.run/docs) for more information. View the [user guide](docs/user_guide.md) for more information.
## ⚙️ How it works ## ⚙️ How it works
Check out the [design document](misc/design.md) to understand Uncloud's design philosophy and goals. Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals.
Here is a diagram of an Uncloud multi-provider cluster of 3 machines: Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
![Diagram: multi-provider cluster of 3 machines](website/landing/images/diagram.webp) ![Diagram: multi-provider cluster of 3 machines](website/images/diagram.webp)
<details> <details>
<summary>Peek under the hood to see what happens when you run certain commands.</summary> <summary>Peek under the hood to see what happens when you run certain commands.</summary>
@@ -286,34 +268,8 @@ and there may be breaking changes between releases.
We'd love your input! Here's how you can contribute: We'd love your input! Here's how you can contribute:
* 🐛 Found a bug? [Open an issue](https://github.com/psviderski/uncloud/issues) * 🐛 Found a bug? [Open an issue](https://github.com/psviderski/uncloud/issues)
* 💡 Have questions, ideas, or need help? * 💡 Have ideas or need help? [Join our Discord community](https://discord.gg/eR35KQJhPu) where we discuss features,
* Start a discussion or join an existing one in roadmap, implementation details, and help each other out.
the [Discussions](https://github.com/psviderski/uncloud/discussions).
* Join our [Discord community](https://discord.gg/eR35KQJhPu) where we discuss features, roadmap, implementation
details, and help each other out.
## 🙏 Inspiration & Acknowledgements
I'm grateful to the following projects that inspired Uncloud's design and implementation:
* [Kamal](https://kamal-deploy.org/) — for proving that even in the declarative era of Kubernetes there is a place for
simple deployment tools that use imperative commands without complex orchestration. Kamal powers the multi-billion
dollar company [37signals](https://37signals.com/) where it was created, and that's truly inspiring!
* [Fly.io](https://fly.io/) — for inspiring my vision for what self-hosted infrastructure should feel like, proving that
developer experience and powerful infrastructure can coexist beautifully.
* [Tailscale](https://tailscale.com/) — for pioneering the vision of decentralised flat mesh networking with an amazing
user experience that feels like magic.
* [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
[grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for its elegant approach to secure WireGuard-based overlay
networking with zero configuration.
* [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/)
— for showing the power of simplicity and pragmatism in container orchestration and that not every problem needs the
complexity of Kubernetes.
Special thanks to the [Corrosion](https://github.com/superfly/corrosion) project by Fly.io for providing the distributed
SQLite database used to share Uncloud's cluster state.
## 📫 Stay updated ## 📫 Stay updated
@@ -323,15 +279,6 @@ SQLite database used to share Uncloud's cluster state.
features, and be the first to know when it's ready for production use. features, and be the first to know when it's ready for production use.
* Watch this repository for releases. * Watch this repository for releases.
## 💖 Sponsors
These companies and projects are helping Uncloud with their generous sponsorship and/or services:
<!-- Sentry -->
<a href="https://sentry.io/welcome/">
<img height="100" alt="Sentry" src="https://github.com/user-attachments/assets/6c1439c0-d20d-40dc-a669-c9aa94651dfa" />
</a>
## ❤️ Contributors ## ❤️ Contributors
Thank you [@cedws](https://github.com/cedws) for being the first contributor to Uncloud! 🎉 Thank you [@cedws](https://github.com/cedws) for being the first contributor to Uncloud! 🎉
+1 -2
View File
@@ -2,9 +2,8 @@ package cluster
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/ucind"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/psviderski/uncloud/internal/ucind"
) )
func NewCreateCommand() *cobra.Command { func NewCreateCommand() *cobra.Command {
+1 -2
View File
@@ -2,9 +2,8 @@ package cluster
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/ucind"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/psviderski/uncloud/internal/ucind"
) )
func NewRemoveCommand() *cobra.Command { func NewRemoveCommand() *cobra.Command {
+1 -1
View File
@@ -13,7 +13,7 @@ func NewRootCommand() *cobra.Command {
} }
cmd.AddCommand( cmd.AddCommand(
NewCreateCommand(), NewCreateCommand(),
// NewListCommand(), //NewListCommand(),
NewRemoveCommand(), NewRemoveCommand(),
) )
return cmd return cmd
-77
View File
@@ -1,77 +0,0 @@
package main
import (
"context"
"fmt"
composecli "github.com/compose-spec/compose-go/v2/cli"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/pkg/client/compose"
"github.com/spf13/cobra"
)
// NewBuildCommand creates a new command to build services from a Compose file.
func NewBuildCommand() *cobra.Command {
opts := cli.BuildOptions{}
cmd := &cobra.Command{
Use: "build [FLAGS] [SERVICE...]",
Short: "Build services from a Compose file.",
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
if len(args) > 0 {
opts.Services = args
}
return runBuild(cmd.Context(), uncli, opts)
},
}
cmd.Flags().StringSliceVarP(&opts.Files, "file", "f", nil,
"One or more Compose files to build (default compose.yaml)")
cmd.Flags().StringSliceVarP(&opts.Profiles, "profile", "p", nil,
"One or more Compose profiles to enable.")
cmd.Flags().BoolVarP(&opts.Push, "push", "P", false,
"Push built images to the registry after building. (default false)")
cmd.Flags().BoolVarP(&opts.NoCache, "no-cache", "n", false,
"Do not use cache when building images. (default false)")
return cmd
}
// TODO: deduplicate with a similar functino for deploy options
// projectOpts returns the project options for the Compose file(s).
func projectOptsFromBuildOpts(opts cli.BuildOptions) []composecli.ProjectOptionsFn {
projectOpts := []composecli.ProjectOptionsFn{}
if len(opts.Profiles) > 0 {
projectOpts = append(projectOpts, composecli.WithDefaultProfiles(opts.Profiles...))
}
return projectOpts
}
// runBuild parses the Compose file(s), builds the services, and pushes them if requested.
func runBuild(ctx context.Context, uncli *cli.CLI, opts cli.BuildOptions) error {
projectOpts := projectOptsFromBuildOpts(opts)
project, err := compose.LoadProject(ctx, opts.Files, projectOpts...)
if err != nil {
return fmt.Errorf("load compose file(s): %w", err)
}
if len(opts.Services) > 0 {
project, err = project.WithSelectedServices(opts.Services)
if err != nil {
return fmt.Errorf("select services: %w", err)
}
}
servicesToBuild := cli.GetServicesThatNeedBuild(project)
if len(servicesToBuild) == 0 {
fmt.Println("No services to build.")
return nil
}
return cli.BuildServices(ctx, servicesToBuild, opts)
}
-76
View File
@@ -1,76 +0,0 @@
package caddy
import (
"context"
"fmt"
"os"
"github.com/alecthomas/chroma/v2/quick"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/pkg/api"
"github.com/spf13/cobra"
)
type configOptions struct {
machine string
noColor bool
context string
}
func NewConfigCommand() *cobra.Command {
opts := configOptions{}
cmd := &cobra.Command{
Use: "config",
Short: "Show the current Caddy configuration (Caddyfile).",
Long: "Display the current Caddy configuration (Caddyfile) from the connected machine or a specified one.",
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return runConfig(cmd.Context(), uncli, opts)
},
}
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
"Name or ID of the machine to get the configuration from. (default is connected machine)")
cmd.Flags().BoolVar(&opts.noColor, "no-color", false,
"Disable syntax highlighting for the output.")
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd
}
func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
clusterClient, err := uncli.ConnectCluster(ctx, opts.context)
if err != nil {
return fmt.Errorf("connect to cluster: %w", err)
}
defer clusterClient.Close()
if opts.machine != "" {
// If a specific machine is requested, use it to get the Caddy configuration.
ctx, _, err = api.ProxyMachinesContext(ctx, clusterClient, []string{opts.machine})
if err != nil {
return err
}
}
config, err := clusterClient.Caddy.GetConfig(ctx, nil)
if err != nil {
return fmt.Errorf("get Caddy config: %w", err)
}
// Print the Caddyfile with syntax highlighting.
if opts.noColor {
fmt.Print(config.Caddyfile)
} else {
if err = quick.Highlight(os.Stdout, config.Caddyfile, "caddy", "terminal256", "monokai"); err != nil {
// If highlighting fails, fall back to plain output.
fmt.Print(config.Caddyfile)
}
}
return nil
}
+4 -17
View File
@@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"maps" "maps"
"os"
"slices" "slices"
"strings" "strings"
@@ -19,10 +18,9 @@ import (
) )
type deployOptions struct { type deployOptions struct {
caddyfile string image string
image string machines []string
machines []string context string
context string
} }
func NewDeployCommand() *cobra.Command { func NewDeployCommand() *cobra.Command {
@@ -39,8 +37,6 @@ func NewDeployCommand() *cobra.Command {
}, },
} }
cmd.Flags().StringVar(&opts.caddyfile, "caddyfile", "",
"Path to a custom global Caddy config (Caddyfile) that will be prepended to the auto-generated Caddy config.")
cmd.Flags().StringVar(&opts.image, "image", "", cmd.Flags().StringVar(&opts.image, "image", "",
"Caddy Docker image to deploy. (default caddy:LATEST_VERSION)") "Caddy Docker image to deploy. (default caddy:LATEST_VERSION)")
cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil, cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil,
@@ -55,15 +51,6 @@ func NewDeployCommand() *cobra.Command {
} }
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error { func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
caddyfile := ""
if opts.caddyfile != "" {
data, err := os.ReadFile(opts.caddyfile)
if err != nil {
return fmt.Errorf("read Caddyfile: %w", err)
}
caddyfile = strings.TrimSpace(string(data))
}
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx, opts.context)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
@@ -104,7 +91,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
placement := api.Placement{ placement := api.Placement{
Machines: cli.ExpandCommaSeparatedValues(opts.machines), Machines: cli.ExpandCommaSeparatedValues(opts.machines),
} }
d, err := clusterClient.NewCaddyDeployment(opts.image, caddyfile, placement) d, err := clusterClient.NewCaddyDeployment(opts.image, placement)
if err != nil { if err != nil {
return fmt.Errorf("create caddy deployment: %w", err) return fmt.Errorf("create caddy deployment: %w", err)
} }
-1
View File
@@ -10,7 +10,6 @@ func NewRootCommand() *cobra.Command {
Short: "Manage Caddy reverse proxy service.", Short: "Manage Caddy reverse proxy service.",
} }
cmd.AddCommand( cmd.AddCommand(
NewConfigCommand(),
NewDeployCommand(), NewDeployCommand(),
) )
return cmd return cmd
+1 -29
View File
@@ -20,8 +20,6 @@ type deployOptions struct {
files []string files []string
profiles []string profiles []string
services []string services []string
noBuild bool
recreate bool
context string context string
} }
@@ -49,11 +47,6 @@ func NewDeployCommand() *cobra.Command {
"One or more Compose profiles to enable.") "One or more Compose profiles to enable.")
cmd.Flags().StringVarP(&opts.context, "context", "c", "", cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context to deploy to (default is the current context)") "Name of the cluster context to deploy to (default is the current context)")
cmd.Flags().BoolVarP(&opts.noBuild, "no-build", "n", false,
"Do not build images before deploying services. (default false)")
cmd.Flags().BoolVar(&opts.recreate, "recreate", false,
"Recreate containers even if their configuration and image haven't changed.")
// TODO: Consider adding a filter flag to specify which machines to deploy to but keep the rest running. // TODO: Consider adding a filter flag to specify which machines to deploy to but keep the rest running.
// Could be useful to test a new version on a subset of machines before rolling out to all. // Could be useful to test a new version on a subset of machines before rolling out to all.
@@ -88,34 +81,13 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
} }
} }
servicesToBuild := cli.GetServicesThatNeedBuild(project)
if len(servicesToBuild) > 0 {
if opts.noBuild {
fmt.Println("Not building services as requested.")
} else {
buildOpts := cli.BuildOptions{
Push: true,
NoCache: false,
}
if err := cli.BuildServices(ctx, servicesToBuild, buildOpts); err != nil {
return fmt.Errorf("build services: %w", err)
}
}
}
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx, opts.context)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
defer clusterClient.Close() defer clusterClient.Close()
var strategy deploy.Strategy composeDeploy, err := compose.NewDeployment(ctx, clusterClient, project)
if opts.recreate {
strategy = &deploy.RollingStrategy{ForceRecreate: true}
}
composeDeploy, err := compose.NewDeploymentWithStrategy(ctx, clusterClient, project, strategy)
if err != nil { if err != nil {
return fmt.Errorf("create compose deployment: %w", err) return fmt.Errorf("create compose deployment: %w", err)
} }
-122
View File
@@ -1,122 +0,0 @@
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
const docsDir = "website/docs/9-cli-reference"
type cmdWrapper struct {
cmd *cobra.Command
}
// NewDocsCommand creates a new hidden command to generate CLI reference docs.
func NewDocsCommand() *cobra.Command {
wrapper := &cmdWrapper{}
cmd := &cobra.Command{
Use: "docs",
Short: "Generate Uncloud CLI reference docs",
SilenceUsage: true,
DisableFlagsInUseLine: true,
Hidden: true,
Args: cobra.NoArgs,
ValidArgsFunction: cobra.NoFileCompletions,
RunE: func(cmd *cobra.Command, _ []string) error {
// Remove existing markdown files.
mdFiles, err := filepath.Glob(filepath.Join(docsDir, "*.md"))
if err != nil {
return fmt.Errorf("list existing CLI docs: %w", err)
}
for _, f := range mdFiles {
if err = os.Remove(f); err != nil {
return fmt.Errorf("remove '%s': %w", f, err)
}
}
// Generate new CLI reference docs.
wrapper.cmd.Root().DisableAutoGenTag = true
if err := doc.GenMarkdownTree(cmd.Root(), docsDir); err != nil {
return fmt.Errorf("generate CLI docs: %w", err)
}
// Remove *completion*.md files that contain malformatted code blocks that break Docusaurus.
mdFiles, err = filepath.Glob(filepath.Join(docsDir, "*completion*.md"))
if err != nil {
return fmt.Errorf("list generated CLI docs: %w", err)
}
for _, f := range mdFiles {
if err = os.Remove(f); err != nil {
return fmt.Errorf("remove '%s': %w", f, err)
}
}
// Post-process generated markdown files.
mdFiles, err = filepath.Glob(filepath.Join(docsDir, "*.md"))
if err != nil {
return fmt.Errorf("list generated CLI docs: %w", err)
}
for _, f := range mdFiles {
if err = postProcessMarkdown(f); err != nil {
return fmt.Errorf("post-process '%s': %w", f, err)
}
}
return nil
},
}
wrapper.cmd = cmd
return cmd
}
// postProcessMarkdown applies transformations to generated markdown files.
func postProcessMarkdown(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return err
}
content := string(data)
// Replace "SEE ALSO" with "See also".
content = strings.ReplaceAll(content, "SEE ALSO", "See also")
// Escape <id> to avoid Docusaurus treating it as an HTML tag.
content = strings.ReplaceAll(content, "<id>", "\\<id>")
// Remove broken links to completion docs.
if strings.Contains(content, "[uc completion") {
lines := strings.Split(content, "\n")
var filteredLines []string
for _, line := range lines {
if !strings.Contains(line, "[uc completion") {
filteredLines = append(filteredLines, line)
}
}
content = strings.Join(filteredLines, "\n")
}
// Adjust heading levels. Process from shortest to longest to avoid double replacements.
replacements := []struct {
old, new string
}{
{`(?m)^## `, `# `},
{`(?m)^### `, `## `},
{`(?m)^#### `, `### `},
{`(?m)^##### `, `#### `},
}
for _, r := range replacements {
re := regexp.MustCompile(r.old)
content = re.ReplaceAllString(content, r.new)
}
return os.WriteFile(filename, []byte(content), 0o644)
}
+8 -13
View File
@@ -41,7 +41,7 @@ func NewAddCommand() *cobra.Command {
if err != nil { if err != nil {
return fmt.Errorf("parse remote machine: %w", err) return fmt.Errorf("parse remote machine: %w", err)
} }
remoteMachine := &cli.RemoteMachine{ remoteMachine := cli.RemoteMachine{
User: user, User: user,
Host: host, Host: host,
Port: port, Port: port,
@@ -59,12 +59,11 @@ func NewAddCommand() *cobra.Command {
cmd.Flags().StringVar( cmd.Flags().StringVar(
&opts.publicIP, "public-ip", "auto", &opts.publicIP, "public-ip", "auto",
"Public IP address of the machine for ingress configuration. Use 'auto' for automatic detection, "+ "Public IP address of the machine for ingress configuration. Use 'auto' for automatic detection, "+
fmt.Sprintf("blank '' or '%s' to disable ingress on this machine, or specify an IP address.", PublicIPNone), "blank '' or 'none' to disable ingress on this machine, or specify an IP address.",
) )
cmd.Flags().StringVarP( cmd.Flags().StringVarP(
&opts.sshKey, "ssh-key", "i", "", &opts.sshKey, "ssh-key", "i", "",
fmt.Sprintf("Path to SSH private key for remote login (if not already added to SSH agent). (default %q)", "path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
cli.DefaultSSHKeyPath),
) )
cmd.Flags().StringVar( cmd.Flags().StringVar(
&opts.version, "version", "latest", &opts.version, "version", "latest",
@@ -78,12 +77,12 @@ func NewAddCommand() *cobra.Command {
return cmd return cmd
} }
func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine, opts addOptions) error { func add(ctx context.Context, uncli *cli.CLI, remoteMachine cli.RemoteMachine, opts addOptions) error {
var publicIP *netip.Addr var publicIP *netip.Addr
switch opts.publicIP { switch opts.publicIP {
case "auto": case "auto":
publicIP = &netip.Addr{} publicIP = &netip.Addr{}
case "", PublicIPNone: case "", "none":
publicIP = nil publicIP = nil
default: default:
ip, err := netip.ParseAddr(opts.publicIP) ip, err := netip.ParseAddr(opts.publicIP)
@@ -93,7 +92,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
publicIP = &ip publicIP = &ip
} }
clusterClient, machineClient, err := uncli.AddMachine(ctx, cli.AddMachineOptions{ machineClient, err := uncli.AddMachine(ctx, cli.AddMachineOptions{
Context: opts.context, Context: opts.context,
MachineName: opts.name, MachineName: opts.name,
PublicIP: publicIP, PublicIP: publicIP,
@@ -103,7 +102,6 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
if err != nil { if err != nil {
return err return err
} }
defer clusterClient.Close()
defer machineClient.Close() defer machineClient.Close()
if opts.noCaddy { if opts.noCaddy {
@@ -119,11 +117,8 @@ 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. Otherwise, use the latest version. // use the deployed image version. Otherwise, use the latest version.
// 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.
// See the issue for more details: https://github.com/psviderski/uncloud/issues/65.
caddyImage := "" caddyImage := ""
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName) caddySvc, err := machineClient.InspectService(ctx, client.CaddyServiceName)
if err != nil { if err != nil {
if !errors.Is(err, api.ErrNotFound) { if !errors.Is(err, api.ErrNotFound) {
return fmt.Errorf("inspect caddy service: %w", err) return fmt.Errorf("inspect caddy service: %w", err)
@@ -146,7 +141,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
// TODO: scale the existing Caddy service to the new machine instead of running a new deployment // TODO: scale the existing Caddy service to the new machine instead of running a new deployment
// that may cause a small downtime. // that may cause a small downtime.
d, err := clusterClient.NewCaddyDeployment(caddyImage, "", api.Placement{}) d, err := machineClient.NewCaddyDeployment(caddyImage, api.Placement{})
if err != nil { if err != nil {
return fmt.Errorf("create caddy deployment: %w", err) return fmt.Errorf("create caddy deployment: %w", err)
} }
-6
View File
@@ -1,6 +0,0 @@
package machine
const (
// PublicIPNone is the value used to indicate removal of public IP
PublicIPNone = "none"
)
+6 -6
View File
@@ -77,19 +77,18 @@ func NewInitCommand() *cobra.Command {
cmd.Flags().StringVar( cmd.Flags().StringVar(
&opts.publicIP, "public-ip", "auto", &opts.publicIP, "public-ip", "auto",
"Public IP address of the machine for ingress configuration. Use 'auto' for automatic detection, "+ "Public IP address of the machine for ingress configuration. Use 'auto' for automatic detection, "+
fmt.Sprintf("blank '' or '%s' to disable ingress on this machine, or specify an IP address.", PublicIPNone), "blank '' or 'none' to disable ingress on this machine, or specify an IP address.",
) )
cmd.Flags().StringVarP( cmd.Flags().StringVarP(
&opts.sshKey, "ssh-key", "i", "", &opts.sshKey, "ssh-key", "i", "",
fmt.Sprintf("Path to SSH private key for remote login (if not already added to SSH agent). (default %q)", "Path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
cli.DefaultSSHKeyPath),
) )
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.",
) )
cmd.Flags().StringVarP( cmd.Flags().StringVarP(
&opts.context, "context", "c", cli.DefaultContextName, &opts.context, "context", "c", "default",
"Name of the created context for the initialised cluster in the Uncloud config.", "Name of the created context for the initialised cluster in the Uncloud config.",
) )
@@ -106,7 +105,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
switch opts.publicIP { switch opts.publicIP {
case "auto": case "auto":
publicIP = &netip.Addr{} publicIP = &netip.Addr{}
case "", PublicIPNone: case "", "none":
publicIP = nil publicIP = nil
default: default:
ip, err := netip.ParseAddr(opts.publicIP) ip, err := netip.ParseAddr(opts.publicIP)
@@ -115,6 +114,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
} }
publicIP = &ip publicIP = &ip
} }
client, err := uncli.InitCluster(ctx, cli.InitClusterOptions{ client, err := uncli.InitCluster(ctx, cli.InitClusterOptions{
Context: opts.context, Context: opts.context,
MachineName: opts.name, MachineName: opts.name,
@@ -147,7 +147,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 := client.NewCaddyDeployment("", api.Placement{})
if err != nil { if err != nil {
return fmt.Errorf("create caddy deployment: %w", err) return fmt.Errorf("create caddy deployment: %w", err)
} }
+4 -5
View File
@@ -14,18 +14,18 @@ import (
) )
func NewListCommand() *cobra.Command { func NewListCommand() *cobra.Command {
var contextName string var clusterContext 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, contextName) return list(cmd.Context(), uncli, clusterContext)
}, },
} }
cmd.Flags().StringVarP( cmd.Flags().StringVarP(
&contextName, "context", "c", "", &clusterContext, "context", "c", "",
"Name of the cluster context. (default is the current context)", "Name of the cluster context. (default is the current context)",
) )
return cmd return cmd
@@ -68,8 +68,7 @@ func list(ctx context.Context, uncli *cli.CLI, clusterName string) error {
} }
if _, err = fmt.Fprintf( if _, err = fmt.Fprintf(
tw, "%s\t%s\t%s\t%s\t%s\n", m.Name, capitalise(member.State.String()), subnet, publicIP, tw, "%s\t%s\t%s\t%s\t%s\n", m.Name, capitalise(member.State.String()), subnet, publicIP, strings.Join(endpoints, ", "),
strings.Join(endpoints, ", "),
); err != nil { ); err != nil {
return fmt.Errorf("write row: %w", err) return fmt.Errorf("write row: %w", err)
} }
-47
View File
@@ -1,47 +0,0 @@
package machine
import (
"context"
"fmt"
"github.com/psviderski/uncloud/internal/cli"
"github.com/spf13/cobra"
)
func NewRenameCommand() *cobra.Command {
var contextName string
cmd := &cobra.Command{
Use: "rename OLD_NAME NEW_NAME",
Short: "Rename a machine in the cluster.",
Long: `Rename a machine in the cluster.
This command changes the name of an existing machine while preserving all other
configuration including network settings, public IP, and cluster membership.`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return rename(cmd.Context(), uncli, contextName, args[0], args[1])
},
}
cmd.Flags().StringVarP(
&contextName, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd
}
func rename(ctx context.Context, uncli *cli.CLI, contextName, oldName, newName string) error {
client, err := uncli.ConnectCluster(ctx, contextName)
if err != nil {
return err
}
defer client.Close()
machine, err := client.RenameMachine(ctx, oldName, newName)
if err != nil {
return fmt.Errorf("rename machine: %w", err)
}
fmt.Printf("Machine %q renamed to %q (ID: %s)\n", oldName, machine.Name, machine.Id)
return nil
}
-249
View File
@@ -1,249 +0,0 @@
package machine
import (
"context"
"errors"
"fmt"
"maps"
"slices"
"strings"
"sync"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/tree"
"github.com/docker/compose/v2/pkg/progress"
"github.com/docker/docker/api/types/container"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/pkg/api"
"github.com/spf13/cobra"
)
type removeOptions struct {
noReset bool
yes bool
context string
}
func NewRmCommand() *cobra.Command {
opts := removeOptions{}
cmd := &cobra.Command{
Use: "rm MACHINE",
Aliases: []string{"remove", "delete"},
Short: "Remove a machine from a cluster and reset it.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return remove(cmd.Context(), uncli, args[0], opts)
},
}
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
"Do not prompt for confirmation before removing the machine.")
cmd.Flags().BoolVar(&opts.noReset, "no-reset", false,
"Do not reset the machine after removing it from the cluster. This will leave all containers and data intact.")
return cmd
}
func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOptions) error {
// TODO: automatically choose a connection to the machine that is not being removed.
client, err := uncli.ConnectCluster(ctx, opts.context)
if err != nil {
return fmt.Errorf("connect to cluster: %w", err)
}
defer client.Close()
// Verify the machine exists and list all service containers on it including stopped ones.
mctx, machines, err := api.ProxyMachinesContext(ctx, client, []string{nameOrID})
if err != nil {
return err
}
if len(machines) == 0 {
return fmt.Errorf("machine '%s' not found in the cluster", nameOrID)
}
m := machines[0].Machine
// Verify if the machine being removed is the proxy machine we're connected to.
proxyMachine, err := client.MachineClient.Inspect(ctx, nil)
if err != nil {
return fmt.Errorf("inspect proxy machine: %w", err)
}
if proxyMachine.Id == m.Id {
allMachines, err := client.ListMachines(ctx, nil)
if err != nil {
return fmt.Errorf("list machines: %w", err)
}
if len(allMachines) > 1 {
return errors.New("cannot remove the machine you are currently connected to. " +
"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")
// It's ok to remove the proxy machine if it's the last one in the cluster.
}
}
// TODO: mark the machine as being removed and unschedulable when this is possible to prevent new containers
// from being scheduled on it while the removal is in progress.
reset := !opts.noReset
var containers []api.ServiceContainer
reachable := false
if reset {
// Check if the machine is up and has service containers.
listOpts := container.ListOptions{All: true}
machineContainers, err := client.Docker.ListServiceContainers(mctx, "", listOpts)
if err == nil {
reachable = true
containers = machineContainers[0].Containers
if len(containers) > 0 {
plural := ""
if len(containers) > 1 {
plural = "s"
}
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
fmt.Println(formatContainerTree(containers))
fmt.Println()
fmt.Println("This will remove all service containers from the machine, remove it from the cluster, " +
"and reset it to the uninitialised state.")
} else {
fmt.Printf("No service containers found on machine '%s'.\n", m.Name)
fmt.Println("This will remove the machine from the cluster and reset it to the uninitialised state.")
}
} else {
fmt.Printf("This will remove machine '%s' from the cluster without resetting it as it's unreachable.\n",
m.Name)
}
} else {
fmt.Printf("This will remove machine '%s' from the cluster without resetting it.\n", m.Name)
}
if !opts.yes {
confirmed, err := cli.Confirm()
if err != nil {
return fmt.Errorf("confirm removal: %w", err)
}
if !confirmed {
fmt.Println("Cancelled. Machine was not removed.")
return nil
}
}
if reset && len(containers) > 0 {
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
return removeContainers(ctx, client, containers)
}, uncli.ProgressOut(), "Removing containers")
if err != nil {
return fmt.Errorf("remove containers: %w", err)
}
fmt.Println()
}
if _, err = client.RemoveMachine(ctx, &pb.RemoveMachineRequest{Id: m.Id}); err != nil {
return fmt.Errorf("remove machine from cluster: %w", err)
}
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
if reset && reachable {
_, err = client.MachineClient.Reset(mctx, &pb.ResetRequest{})
if err != nil {
fmt.Printf("WARNING: Failed to reset machine: %v\n", err)
} else {
fmt.Println("Machine reset initiated and will complete in the background.")
}
}
// TODO: remove the connection to the machine from the uncloud config if it exists. We need a way to associate
// the machine with its connection in the config, e.g. by storing the machine name in the connection metadata.
// TODO: If Caddy was running on this machine and a cluster domain is reserved,
// let the user know that the DNS records should be updated.
return nil
}
// formatContainerTree formats a list of containers grouped by service as a tree structure.
func formatContainerTree(containers []api.ServiceContainer) string {
if len(containers) == 0 {
return ""
}
// Group containers by service.
serviceContainers := make(map[string][]api.ServiceContainer)
for _, ctr := range containers {
serviceName := ctr.ServiceName()
serviceContainers[serviceName] = append(serviceContainers[serviceName], ctr)
}
// Build tree output.
var output []string
serviceNames := slices.Sorted(maps.Keys(serviceContainers))
for _, serviceName := range serviceNames {
ctrs := serviceContainers[serviceName]
mode := ctrs[0].ServiceMode()
// Format a tree for the service with its containers.
plural := ""
if len(ctrs) > 1 {
plural = "s"
}
t := tree.Root(fmt.Sprintf("• %s (%s, %d container%s)", serviceName, mode, len(ctrs), plural)).
EnumeratorStyle(lipgloss.NewStyle().MarginLeft(2).MarginRight(1))
// Add containers as children.
for _, ctr := range ctrs {
state, _ := ctr.HumanState()
info := fmt.Sprintf("%s • %s • %s", ctr.Name, ctr.Config.Image, state)
t.Child(info)
}
output = append(output, t.String())
}
return strings.Join(output, "\n")
}
// removeContainers removes the given service containers from the machine.
func removeContainers(ctx context.Context, client api.Client, containers []api.ServiceContainer) error {
if len(containers) == 0 {
return nil
}
wg := sync.WaitGroup{}
errCh := make(chan error)
for _, ctr := range containers {
wg.Add(1)
go func(c api.ServiceContainer) {
defer wg.Done()
// Gracefully stop the container before removing it.
err := client.StopContainer(ctx, c.ServiceID(), c.ID, container.StopOptions{})
if err != nil && !errors.Is(err, api.ErrNotFound) {
errCh <- fmt.Errorf("stop container '%s': %w", c.ID, err)
}
err = client.RemoveContainer(ctx, c.ServiceID(), c.ID, container.RemoveOptions{
// Remove anonymous volumes created by the container.
RemoveVolumes: true,
})
if err != nil && !errors.Is(err, api.ErrNotFound) {
errCh <- fmt.Errorf("remove container '%s': %w", c.ID, err)
}
}(ctr)
}
go func() {
wg.Wait()
close(errCh)
}()
var err error
for e := range errCh {
err = errors.Join(err, e)
}
return err
}
+2 -6
View File
@@ -6,17 +6,13 @@ import (
func NewRootCommand() *cobra.Command { func NewRootCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "machine", Use: "machine",
Aliases: []string{"m"}, Short: "Manage machines in an Uncloud cluster.",
Short: "Manage machines in an Uncloud cluster.",
} }
cmd.AddCommand( cmd.AddCommand(
NewAddCommand(), NewAddCommand(),
NewInitCommand(), NewInitCommand(),
NewListCommand(), NewListCommand(),
NewRenameCommand(),
NewRmCommand(),
NewUpdateCommand(),
NewTokenCommand(), NewTokenCommand(),
) )
return cmd return cmd
+1 -2
View File
@@ -2,10 +2,9 @@ package machine
import ( import (
"fmt" "fmt"
"github.com/spf13/cobra"
"github.com/psviderski/uncloud/internal/daemon" "github.com/psviderski/uncloud/internal/daemon"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
"github.com/spf13/cobra"
) )
type tokenOptions struct { type tokenOptions struct {
-128
View File
@@ -1,128 +0,0 @@
package machine
import (
"context"
"fmt"
"net/netip"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/spf13/cobra"
)
type updateOptions struct {
name string
publicIP string
context string
}
func NewUpdateCommand() *cobra.Command {
opts := updateOptions{}
cmd := &cobra.Command{
Use: "update",
Short: "Update machine configuration in the cluster.",
Long: `Update machine configuration in the cluster.
This command allows setting various machine properties including:
- Machine name (--name)
- Public IP address (--public-ip)
At least one flag must be specified to perform an update operation.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
return update(cmd.Context(), uncli, cmd, opts, args[0])
},
}
cmd.Flags().StringVar(
&opts.name, "name", "",
"New name for the machine",
)
cmd.Flags().StringVar(
&opts.publicIP, "public-ip", "",
fmt.Sprintf("Public IP address of the machine for ingress configuration. Use '%s' or '' to remove the public IP.", PublicIPNone),
)
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd
}
func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts updateOptions, machineNameOrID string) error {
// Check if at least one flag was explicitly set
if !cmd.Flags().Changed("name") && !cmd.Flags().Changed("public-ip") {
return fmt.Errorf("at least one update flag must be specified (--name, --public-ip)")
}
client, err := uncli.ConnectCluster(ctx, opts.context)
if err != nil {
return err
}
defer client.Close()
// First, resolve the machine to get its ID
machine, err := client.InspectMachine(ctx, machineNameOrID)
if err != nil {
return fmt.Errorf("find machine: %w", err)
}
// Build the update request
req := &pb.UpdateMachineRequest{
MachineId: machine.Machine.Id,
}
if opts.name != "" {
req.Name = &opts.name
}
// Check if --public-ip flag was explicitly provided
if cmd.Flags().Changed("public-ip") {
if opts.publicIP == "" || opts.publicIP == PublicIPNone {
req.PublicIp = &pb.IP{} // Empty IP to signal removal
} else {
// Parse and validate the public IP
ip, err := netip.ParseAddr(opts.publicIP)
if err != nil {
return fmt.Errorf("invalid public IP address %q: %w", opts.publicIP, err)
}
req.PublicIp = pb.NewIP(ip)
}
}
// Perform the update operation
updatedMachine, err := client.UpdateMachine(ctx, req)
if err != nil {
return fmt.Errorf("update machine: %w", err)
}
// Report what was changed
changes := make([]string, 0)
if opts.name != "" {
changes = append(changes, fmt.Sprintf("name: %q -> %q", machine.Machine.Name, updatedMachine.Name))
}
if cmd.Flags().Changed("public-ip") {
oldIP := PublicIPNone
if machine.Machine.PublicIp != nil {
if addr, err := machine.Machine.PublicIp.ToAddr(); err == nil {
oldIP = addr.String()
}
}
newIP := PublicIPNone
if updatedMachine.PublicIp != nil {
if addr, err := updatedMachine.PublicIp.ToAddr(); err == nil {
newIP = addr.String()
}
}
changes = append(changes, fmt.Sprintf("public IP: %s -> %s", oldIP, newIP))
}
fmt.Printf("Machine %q (ID: %s) configuration updated:\n", updatedMachine.Name, updatedMachine.Id)
for _, change := range changes {
fmt.Printf(" %s\n", change)
}
return nil
}
+1 -3
View File
@@ -27,7 +27,7 @@ type globalOptions struct {
func main() { func main() {
opts := globalOptions{} opts := globalOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "uc", Use: "uncloud",
Short: "A CLI tool for managing Uncloud resources such as clusters, machines, and services.", Short: "A CLI tool for managing Uncloud resources such as clusters, machines, and services.",
Version: version.String(), Version: version.String(),
SilenceUsage: true, SilenceUsage: true,
@@ -75,8 +75,6 @@ func main() {
cmd.AddCommand( cmd.AddCommand(
NewDeployCommand(), NewDeployCommand(),
NewDocsCommand(),
NewBuildCommand(),
caddy.NewRootCommand(), caddy.NewRootCommand(),
cmdcontext.NewRootCommand(), cmdcontext.NewRootCommand(),
dns.NewRootCommand(), dns.NewRootCommand(),
+1
View File
@@ -9,6 +9,7 @@ import (
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/psviderski/uncloud/internal/cli" "github.com/psviderski/uncloud/internal/cli"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
+4 -24
View File
@@ -17,7 +17,6 @@ import (
) )
type runOptions struct { type runOptions struct {
caddyfile string
command []string command []string
cpu dockeropts.NanoCPUs cpu dockeropts.NanoCPUs
entrypoint string entrypoint string
@@ -58,9 +57,6 @@ func NewRunCommand() *cobra.Command {
}, },
} }
cmd.Flags().StringVar(&opts.caddyfile, "caddyfile", "",
"Path to a custom Caddy config (Caddyfile) for the service. "+
"Cannot be used together with non-@host published ports.")
cmd.Flags().VarP(&opts.cpu, "cpu", "", cmd.Flags().VarP(&opts.cpu, "cpu", "",
"Maximum number of CPU cores a service container can use. Fractional values are allowed: "+ "Maximum number of CPU cores a service container can use. Fractional values are allowed: "+
"0.5 for half a core or 2.25 for two and a quarter cores.") "0.5 for half a core or 2.25 for two and a quarter cores.")
@@ -86,14 +82,13 @@ func NewRunCommand() *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:][load_balancer_port:]container_port[/protocol] or [host_ip:]:host_port:container_port[/protocol]@host\n"+
"Supported protocols: tcp, udp, http, https (default is tcp). If a hostname for http(s) port is not specified\n"+ "Supported protocols: tcp, udp, http, https (default is tcp). If a hostname for http(s) port is not specified\n"+
"and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+ "and a cluster domain is reserved, service-name.cluster-domain will be used as the hostname.\n"+
"Examples:\n"+ "Examples:\n"+
" -p 8080/https Publish port 8080 as HTTPS via reverse proxy with default service-name.cluster-domain hostname\n"+ " -p 8080/https Publish port 8080 as HTTPS via load balancer with default service-name.cluster-domain hostname\n"+
" -p app.example.com:8080/https Publish port 8080 as HTTPS via reverse proxy with custom hostname\n"+ " -p app.example.com:8080/https Publish port 8080 as HTTPS via load balancer with custom hostname\n"+
// TODO: add support for publishing L4 tcp/udp ports. " -p 9000:8080 Publish port 8080 as TCP port 9000 via load balancer\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")
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').",
@@ -165,15 +160,6 @@ func run(ctx context.Context, uncli *cli.CLI, opts runOptions) error {
func prepareServiceSpec(opts runOptions) (api.ServiceSpec, error) { func prepareServiceSpec(opts runOptions) (api.ServiceSpec, error) {
var spec api.ServiceSpec var spec api.ServiceSpec
caddyfile := ""
if opts.caddyfile != "" {
data, err := os.ReadFile(opts.caddyfile)
if err != nil {
return spec, fmt.Errorf("read Caddyfile: %w", err)
}
caddyfile = strings.TrimSpace(string(data))
}
env, err := parseEnv(opts.env) env, err := parseEnv(opts.env)
if err != nil { if err != nil {
return spec, err return spec, err
@@ -231,12 +217,6 @@ func prepareServiceSpec(opts runOptions) (api.ServiceSpec, error) {
Volumes: volumes, Volumes: volumes,
} }
if caddyfile != "" {
spec.Caddy = &api.CaddySpec{
Config: caddyfile,
}
}
// Overwrite the default ENTRYPOINT of the image or reset it if an empty string is passed. // Overwrite the default ENTRYPOINT of the image or reset it if an empty string is passed.
if opts.entrypoint != "" { if opts.entrypoint != "" {
spec.Container.Entrypoint = []string{opts.entrypoint} spec.Container.Entrypoint = []string{opts.entrypoint}
+1
View File
@@ -55,4 +55,5 @@ func main() {
}() }()
cobra.CheckErr(cmd.ExecuteContext(ctx)) cobra.CheckErr(cmd.ExecuteContext(ctx))
} }
+41
View File
@@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
View File
+9
View File
@@ -0,0 +1,9 @@
---
slug: /
---
# Overview
Uncloud is lightweight tool for creating a cluster of machines running Docker and deploying web applications across
them. It is designed to be simple and easy to use, while still providing the essential features to manage and scale
your apps in production.
@@ -10,12 +10,12 @@ import {themes as prismThemes} from 'prism-react-renderer';
/** @type {import('@docusaurus/types').Config} */ /** @type {import('@docusaurus/types').Config} */
const config = { const config = {
title: 'Uncloud', title: 'Uncloud Docs',
tagline: 'Self-host and scale web apps without Kubernetes complexity', tagline: 'Dinosaurs are cool',
favicon: 'img/favicon.png', favicon: 'img/favicon.png',
// Set the production url of your site here // Set the production url of your site here
url: 'https://uncloud.run', url: 'https://docs.uncloud.run',
// Set the /<baseUrl>/ pathname under which your site is served // Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/' // For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/', baseUrl: '/',
@@ -54,30 +54,27 @@ const config = {
({ ({
docs: { docs: {
// Remove this to remove the "edit this page" links. // Remove this to remove the "edit this page" links.
editUrl: 'https://github.com/psviderski/uncloud/edit/main/website/', editUrl: 'https://github.com/psviderski/uncloud/edit/main/docs/',
// Serve the docs at the site's root. // Serve the docs at the site's root.
// routeBasePath: '/', routeBasePath: '/',
showLastUpdateTime: true, showLastUpdateTime: true,
sidebarPath: './sidebars.js', sidebarPath: './sidebars.js',
}, },
blog: { // blog: {
blogDescription: 'Blog posts from the Uncloud team and community', // showReadingTime: true,
blogSidebarTitle: 'All posts', // feedOptions: {
blogSidebarCount: 'ALL', // type: ['rss', 'atom'],
showReadingTime: true, // xslt: true,
feedOptions: { // },
type: ['rss', 'atom'], // // Please change this to your repo.
xslt: true, // // Remove this to remove the "edit this page" links.
}, // editUrl:
// Please change this to your repo. // 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
// Remove this to remove the "edit this page" links. // // Useful options to enforce blogging best practices
// editUrl: // onInlineTags: 'warn',
// 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', // onInlineAuthors: 'warn',
// Useful options to enforce blogging best practices // onUntruncatedBlogPosts: 'warn',
onInlineTags: 'warn', // },
onInlineAuthors: 'warn',
onUntruncatedBlogPosts: 'warn',
},
theme: { theme: {
customCss: './src/css/custom.css', customCss: './src/css/custom.css',
}, },
@@ -99,24 +96,12 @@ const config = {
// Relative to your site's "static" directory. Cannot be SVGs. Can be external URLs too. // Relative to your site's "static" directory. Cannot be SVGs. Can be external URLs too.
image: 'img/social-card.png', image: 'img/social-card.png',
navbar: { navbar: {
title: 'Uncloud', title: 'Uncloud Docs',
logo: { logo: {
alt: 'Uncloud Logo', alt: 'Uncloud Logo',
src: 'img/logo.svg', src: 'img/logo.svg',
href: 'https://uncloud.run',
}, },
items: [ items: [
{
type: 'doc',
docId: 'overview',
label: 'Docs',
position: 'left',
},
{
to: 'blog',
label: 'Blog',
position: 'left',
},
{ {
type: 'search', type: 'search',
position: 'right', position: 'right',
@@ -159,8 +144,8 @@ const config = {
] ]
}, },
prism: { prism: {
theme: prismThemes.palenight, theme: prismThemes.github,
//darkTheme: prismThemes.dracula, darkTheme: prismThemes.dracula,
}, },
}), }),
}; };
+18434
View File
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
{
"name": "docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "3.7.0",
"@docusaurus/preset-classic": "3.7.0",
"@easyops-cn/docusaurus-search-local": "^0.49.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.7.0",
"@docusaurus/types": "3.7.0"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=18.0"
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
*/ */
const sidebars = { const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure // By default, Docusaurus generates a sidebar from the docs folder structure
sidebar: [{type: 'autogenerated', dirName: '.'}], tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually // But you can create a sidebar manually
/* /*
@@ -11,26 +11,26 @@
--ifm-font-family-base: 'Inter', sans-serif; --ifm-font-family-base: 'Inter', sans-serif;
--ifm-heading-font-family: 'Inter', sans-serif; --ifm-heading-font-family: 'Inter', sans-serif;
--ifm-color-primary: #006be6; --ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #0060cf; --ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #005bc4; --ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #004ba1; --ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #0076fd; --ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #0a7cff; --ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #2c8eff; --ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%; --ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
} }
/* For readability concerns, you should choose a lighter palette in dark mode. */ /* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] { [data-theme='dark'] {
--ifm-color-primary: #3391ee; --ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #1883ec; --ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #137be3; --ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1066bb; --ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #4e9ff0; --ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #5ba7f1; --ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #83bcf5; --ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
} }

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

+1 -2
View File
@@ -20,7 +20,7 @@ Uncloud stores its configuration in `~/.config/uncloud/config.yaml`. If you wish
### Initialisation ### Initialisation
Begin by initialising the first node in your cluster with `uc machine init [USER@HOST:PORT]`. If you do not have a need for Caddy reverse proxy, you may disable this feature with `--no-caddy`. If you want to avoid using uncloud's managed DNS service, add the `--no-dns` flag. Begin by initialising the first node in your cluster with `uc machine init [USER@HOST:PORT]`. If you do not have a need for Caddy reverse proxy, you may disable this feature with `--no-caddy`.
This command will idempotently install Docker, uncloudd, uncloud-corrosion. If Caddy is enabled, it will set up a reverse proxy. If Uncloud DNS is enabled, it will create a DNS A record for the machine's public IP address under `*.[CLUSTER ID].cluster.uncloud.run`. This command will idempotently install Docker, uncloudd, uncloud-corrosion. If Caddy is enabled, it will set up a reverse proxy. If Uncloud DNS is enabled, it will create a DNS A record for the machine's public IP address under `*.[CLUSTER ID].cluster.uncloud.run`.
@@ -37,7 +37,6 @@ Uncloud (uncloud.run) DNS can be managed with the `uc dns` subcommand.
* To reserve a domain name, run `uc dns reserve` * To reserve a domain name, run `uc dns reserve`
* To release a domain name, run `uc dns release`. * To release a domain name, run `uc dns release`.
* To see the domain name, run `uc dns show` * To see the domain name, run `uc dns show`
* To avoid using the Uncloud managed DNS service, use the `--no-dns` flag on your `uc machine init` command.
### Running a service ### Running a service
+2 -3
View File
@@ -3,11 +3,10 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"time"
"github.com/hashicorp/serf/serf" "github.com/hashicorp/serf/serf"
crdt "github.com/ipfs/go-ds-crdt" crdt "github.com/ipfs/go-ds-crdt"
"log/slog"
"time"
) )
// Implements the Broadcaster interface. // Implements the Broadcaster interface.
+1 -2
View File
@@ -2,10 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/dgraph-io/badger/v3"
"log" "log"
"time" "time"
"github.com/dgraph-io/badger/v3"
) )
func customTimeEncoder(t time.Time) string { func customTimeEncoder(t time.Time) string {
+1 -2
View File
@@ -2,10 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/ipfs/go-log/v2"
"log/slog" "log/slog"
"os" "os"
"github.com/ipfs/go-log/v2"
) )
// ipfsLogger is an slog logger that implements the IPFS go-log StandardLogger interface. // ipfsLogger is an slog logger that implements the IPFS go-log StandardLogger interface.
+1 -2
View File
@@ -2,13 +2,12 @@ package main
import ( import (
"fmt" "fmt"
"github.com/docker/docker/libnetwork/networkdb"
"log/slog" "log/slog"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"github.com/docker/docker/libnetwork/networkdb"
) )
func main() { func main() {
+12 -13
View File
@@ -4,13 +4,6 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"log/slog"
"net"
"os"
"os/signal"
"syscall"
"time"
"github.com/hashicorp/memberlist" "github.com/hashicorp/memberlist"
"github.com/hashicorp/serf/cmd/serf/command/agent" "github.com/hashicorp/serf/cmd/serf/command/agent"
"github.com/hashicorp/serf/serf" "github.com/hashicorp/serf/serf"
@@ -18,6 +11,12 @@ import (
badger "github.com/ipfs/go-ds-badger3" badger "github.com/ipfs/go-ds-badger3"
crdt "github.com/ipfs/go-ds-crdt" crdt "github.com/ipfs/go-ds-crdt"
"github.com/lmittmann/tint" "github.com/lmittmann/tint"
"log/slog"
"net"
"os"
"os/signal"
"syscall"
"time"
) )
func createSerfAgentConfig(name, bindAddr, rpcAddr, profile string) *agent.Config { func createSerfAgentConfig(name, bindAddr, rpcAddr, profile string) *agent.Config {
@@ -50,9 +49,9 @@ func createSerfAgent(config *agent.Config) (*agent.Agent, error) {
serfConfig.MemberlistConfig.BindAddr = bindIP serfConfig.MemberlistConfig.BindAddr = bindIP
serfConfig.MemberlistConfig.BindPort = bindPort serfConfig.MemberlistConfig.BindPort = bindPort
// serfConfig.MemberlistConfig.AdvertiseAddr = advertiseIP //serfConfig.MemberlistConfig.AdvertiseAddr = advertiseIP
// serfConfig.MemberlistConfig.AdvertisePort = advertisePort //serfConfig.MemberlistConfig.AdvertisePort = advertisePort
// serfConfig.MemberlistConfig.SecretKey = encryptKey //serfConfig.MemberlistConfig.SecretKey = encryptKey
serfConfig.NodeName = config.NodeName serfConfig.NodeName = config.NodeName
serfConfig.Tags = config.Tags serfConfig.Tags = config.Tags
serfConfig.SnapshotPath = config.SnapshotPath serfConfig.SnapshotPath = config.SnapshotPath
@@ -130,7 +129,7 @@ func main() {
logger := slog.New(tint.NewHandler(os.Stdout, &tint.Options{ logger := slog.New(tint.NewHandler(os.Stdout, &tint.Options{
AddSource: true, AddSource: true,
Level: slog.LevelDebug, Level: slog.LevelDebug,
// Level: slog.LevelInfo, //Level: slog.LevelInfo,
TimeFormat: time.RFC3339Nano, TimeFormat: time.RFC3339Nano,
})) }))
slog.SetDefault(logger) slog.SetDefault(logger)
@@ -165,7 +164,7 @@ func main() {
opts := crdt.DefaultOptions() opts := crdt.DefaultOptions()
opts.Logger = newIPFSLogger(logger) opts.Logger = newIPFSLogger(logger)
// opts.MultiHeadProcessing = true //opts.MultiHeadProcessing = true
// TODO: debug why the heads count may grow on the receiving side if the event backlog is huge and the processing // TODO: debug why the heads count may grow on the receiving side if the event backlog is huge and the processing
// is slow. // is slow.
store, err := crdt.New(localStore, ds.NewKey("/"), syncer, broadcaster, opts) store, err := crdt.New(localStore, ds.NewKey("/"), syncer, broadcaster, opts)
@@ -173,7 +172,7 @@ func main() {
panic(err) panic(err)
} }
// ticker := time.NewTicker(10 * time.Millisecond) //ticker := time.NewTicker(10 * time.Millisecond)
ticker := time.NewTicker(3 * time.Second) ticker := time.NewTicker(3 * time.Second)
go func() { go func() {
for { for {
+5 -4
View File
@@ -4,19 +4,18 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"github.com/hashicorp/serf/serf" "github.com/hashicorp/serf/serf"
"github.com/ipfs/boxo/datastore/dshelp" "github.com/ipfs/boxo/datastore/dshelp"
dag "github.com/ipfs/boxo/ipld/merkledag" dag "github.com/ipfs/boxo/ipld/merkledag"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
ds "github.com/ipfs/go-datastore" ds "github.com/ipfs/go-datastore"
ipld "github.com/ipfs/go-ipld-format" ipld "github.com/ipfs/go-ipld-format"
"log/slog"
) )
// Implements the DAGService interface. // Implements the DAGService interface.
// TODO: implement SessionDAGService to optimize node fetching. // TODO: implement SessionDAGService to optimize node fetching.
// TODO: persistentSerfDAG? // TOOD: persistentSerfDAG?
type dagSyncer struct { type dagSyncer struct {
// Persistent storage for the nodes. // Persistent storage for the nodes.
store ds.Datastore store ds.Datastore
@@ -51,7 +50,8 @@ func (d *dagSyncer) Get(ctx context.Context, cid cid.Cid) (ipld.Node, error) {
} }
slog.Debug("Queried node from peers", "cid", cid, "deadline", query.Deadline()) slog.Debug("Queried node from peers", "cid", cid, "deadline", query.Deadline())
for { responded := false
for !responded {
select { select {
case resp, ok := <-query.ResponseCh(): case resp, ok := <-query.ResponseCh():
if !ok { if !ok {
@@ -63,6 +63,7 @@ func (d *dagSyncer) Get(ctx context.Context, cid cid.Cid) (ipld.Node, error) {
continue continue
} }
slog.Debug("Received node from peer", "cid", cid, "peer", resp.From) slog.Debug("Received node from peer", "cid", cid, "peer", resp.From)
responded = true
query.Close() query.Close()
node, err = nodeFromBytes(resp.Payload) node, err = nodeFromBytes(resp.Payload)
+3 -4
View File
@@ -6,13 +6,12 @@ import (
"crypto/cipher" "crypto/cipher"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/netip"
"time"
"github.com/psviderski/uncloud/internal/machine/network"
"github.com/siderolabs/discovery-api/api/v1alpha1/client/pb" "github.com/siderolabs/discovery-api/api/v1alpha1/client/pb"
discovery "github.com/siderolabs/discovery-client/pkg/client" discovery "github.com/siderolabs/discovery-client/pkg/client"
"go.uber.org/zap" "go.uber.org/zap"
"net/netip"
"time"
"github.com/psviderski/uncloud/internal/machine/network"
) )
const ( const (
+4 -7
View File
@@ -2,11 +2,12 @@ module github.com/psviderski/uncloud
go 1.23.0 go 1.23.0
toolchain go1.23.2
require ( require (
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/Masterminds/semver v1.5.0 github.com/Masterminds/semver v1.5.0
github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/squirrel v1.5.4
github.com/alecthomas/chroma/v2 v2.20.0
github.com/caddyserver/caddy/v2 v2.8.4 github.com/caddyserver/caddy/v2 v2.8.4
github.com/cenkalti/backoff/v4 v4.3.0 github.com/cenkalti/backoff/v4 v4.3.0
github.com/charmbracelet/huh v0.6.0 github.com/charmbracelet/huh v0.6.0
@@ -36,8 +37,6 @@ require (
github.com/jmoiron/sqlx v1.4.0 github.com/jmoiron/sqlx v1.4.0
github.com/lmittmann/tint v1.0.5 github.com/lmittmann/tint v1.0.5
github.com/miekg/dns v1.1.65 github.com/miekg/dns v1.1.65
github.com/mitchellh/mapstructure v1.5.0
github.com/moby/term v0.5.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/image-spec v1.1.0
github.com/siderolabs/discovery-api v0.1.4 github.com/siderolabs/discovery-api v0.1.4
@@ -109,7 +108,6 @@ require (
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/buildx v0.18.0 // indirect github.com/docker/buildx v0.18.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect
@@ -207,6 +205,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.17.2 // indirect github.com/moby/buildkit v0.17.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
@@ -214,8 +213,7 @@ require (
github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.3.0 // indirect github.com/moby/term v0.5.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
@@ -267,7 +265,6 @@ require (
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect
-15
View File
@@ -53,12 +53,6 @@ github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdII
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -111,8 +105,6 @@ github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@@ -161,8 +153,6 @@ github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY= github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=
github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a h1:JMdM89Udp/cOl5tC3MuUJXTPE/nAdU1oyt9jRU44qq8= github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a h1:JMdM89Udp/cOl5tC3MuUJXTPE/nAdU1oyt9jRU44qq8=
github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
@@ -265,8 +255,6 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/buildx v0.18.0 h1:rSauXHeJt90NvtXrLK5J992Eb0UPJZs2vV3u1zTf1nE= github.com/docker/buildx v0.18.0 h1:rSauXHeJt90NvtXrLK5J992Eb0UPJZs2vV3u1zTf1nE=
github.com/docker/buildx v0.18.0/go.mod h1:JGNSshOhHs5FhG3u51jXUf4lLOeD2QBIlJ2vaRB67p4= github.com/docker/buildx v0.18.0/go.mod h1:JGNSshOhHs5FhG3u51jXUf4lLOeD2QBIlJ2vaRB67p4=
github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM=
@@ -495,8 +483,6 @@ github.com/hashicorp/memberlist v0.5.1 h1:mk5dRuzeDNis2bi6LLoQIXfMH7JQvAzt3mQD0v
github.com/hashicorp/memberlist v0.5.1/go.mod h1:zGDXV6AqbDTKTM6yxW0I4+JtFzZAJVoIPvss4hV8F24= github.com/hashicorp/memberlist v0.5.1/go.mod h1:zGDXV6AqbDTKTM6yxW0I4+JtFzZAJVoIPvss4hV8F24=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
@@ -1049,7 +1035,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-176
View File
@@ -1,176 +0,0 @@
package cli
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/distribution/reference"
"github.com/docker/cli/cli/config"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
dockerclient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"github.com/moby/term"
)
type BuildOptions struct {
Files []string
Profiles []string
Services []string
Push bool
NoCache bool
}
// GetServicesThatNeedBuild returns a map of services that require building
func GetServicesThatNeedBuild(project *composetypes.Project) map[string]composetypes.ServiceConfig {
servicesToBuild := make(map[string]composetypes.ServiceConfig, len(project.Services))
for serviceName, service := range project.Services {
if service.Build == nil {
continue
}
servicesToBuild[serviceName] = service
}
return servicesToBuild
}
// BuildServices builds the services defined in the provided map.
func BuildServices(ctx context.Context, servicesToBuild map[string]composetypes.ServiceConfig, opts BuildOptions) error {
fmt.Println("Building services...")
// Init docker client (can be local or remote, depending on DOCKER_HOST environment variable)
dockerCli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation())
if err != nil {
return err
}
defer dockerCli.Close()
serviceImages := make(map[string]string, len(servicesToBuild))
// Build the services using the local docker client and compose libraries
for _, service := range servicesToBuild {
fmt.Printf("Building service: %s\n", service.Name)
imageName, err := buildSingleService(ctx, dockerCli, service, opts)
if err != nil {
return fmt.Errorf("build service %s: %w", service.Name, err)
}
serviceImages[service.Name] = imageName
}
fmt.Printf("Service images are built.\n")
if opts.Push {
err = pushServiceImages(ctx, dockerCli, serviceImages)
}
return err
}
// buildSingleService builds a single service using the Docker client and Compose libraries.
func buildSingleService(ctx context.Context, dockerCli *dockerclient.Client, service composetypes.ServiceConfig, opts BuildOptions) (string, error) {
if service.Build == nil {
return "", fmt.Errorf("service %s has no build configuration", service.Name)
}
if service.Image == "" {
return "", fmt.Errorf("service %s has no image specified; building services without image is not supported yet", service.Name)
}
buildContextPath := service.Build.Context
imageName := service.Image
// Create a tar archive of the build context
buildContext, err := archive.TarWithOptions(buildContextPath, &archive.TarOptions{})
if err != nil {
return "", fmt.Errorf("failed to create build context for service %s: %w", service.Name, err)
}
buildOptions := types.ImageBuildOptions{
// TODO: Support Dockerfiles outside the build context
// See https://github.com/docker/compose/blob/cf89fd1aa1328d5af77658ccc5a1e1b29981ae80/pkg/compose/build_classic.go#L92
Dockerfile: service.Build.Dockerfile,
Tags: []string{imageName},
Remove: true, // Remove intermediate containers
NoCache: opts.NoCache,
}
buildResponse, err := dockerCli.ImageBuild(ctx, buildContext, buildOptions)
if err != nil {
return "", fmt.Errorf("failed to build image for service %s: %w", service.Name, err)
}
defer buildResponse.Body.Close()
// Display the build response
fd, isTerminal := term.GetFdInfo(os.Stdout)
if err := jsonmessage.DisplayJSONMessagesStream(buildResponse.Body, os.Stdout, fd, isTerminal, nil); err != nil {
return "", fmt.Errorf("failed to display build response for service %s: %w", service.Name, err)
}
return imageName, nil
}
// pushSingleServiceImage pushes a single service image.
func pushSingleServiceImage(ctx context.Context, dockerCli *dockerclient.Client, serviceName string, imageName string) error {
ref, err := reference.ParseNormalizedNamed(imageName)
if err != nil {
return err
}
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
registryKey := repoInfo.Index.Name
if repoInfo.Index.Official {
registryKey = registry.IndexServer
}
// Load the Docker config file with auth details, if available
configFile := config.LoadDefaultConfigFile(os.Stderr)
authConfig, err := configFile.GetAuthConfig(registryKey)
if err != nil {
return err
}
authJSON, err := json.Marshal(authConfig)
if err != nil {
return fmt.Errorf("failed to marshal auth config for registry %s: %w", registryKey, err)
}
authStr := base64.URLEncoding.EncodeToString(authJSON)
pushOptions := image.PushOptions{
RegistryAuth: authStr,
}
pushResponse, err := dockerCli.ImagePush(ctx, imageName, pushOptions)
if err != nil {
return fmt.Errorf("failed to push image %s: %w", imageName, err)
}
defer pushResponse.Close()
fmt.Printf("Pushing image %s for service %s...\n", imageName, serviceName)
fd, isTerminal := term.GetFdInfo(os.Stdout)
if err := jsonmessage.DisplayJSONMessagesStream(pushResponse, os.Stdout, fd, isTerminal, nil); err != nil {
return fmt.Errorf("failed to display push response for image %s: %w", imageName, err)
}
fmt.Printf("Image %s pushed successfully.\n", imageName)
return nil
}
// pushServiceImages pushes all built service images to the registry.
func pushServiceImages(ctx context.Context, dockerCli *dockerclient.Client, serviceImages map[string]string) error {
fmt.Printf("Pushing images...\n")
for serviceName, imageName := range serviceImages {
if err := pushSingleServiceImage(ctx, dockerCli, serviceName, imageName); err != nil {
return fmt.Errorf("push image for service %s: %w", serviceName, err)
}
}
return nil
}
+58 -87
View File
@@ -6,8 +6,8 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"slices"
"github.com/charmbracelet/huh"
"github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/streams"
"github.com/psviderski/uncloud/internal/cli/config" "github.com/psviderski/uncloud/internal/cli/config"
"github.com/psviderski/uncloud/internal/fs" "github.com/psviderski/uncloud/internal/fs"
@@ -22,12 +22,7 @@ import (
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
) )
const ( const defaultContextName = "default"
// DefaultSSHKeyPath is the fallback location for the SSH private key when provisioning remote machines.
// Used when no key is explicitly provided and SSH agent authentication fails.
DefaultSSHKeyPath = "~/.ssh/id_ed25519"
DefaultContextName = "default"
)
type CLI struct { type CLI struct {
Config *config.Config Config *config.Config
@@ -170,12 +165,15 @@ func (cli *CLI) InitCluster(ctx context.Context, opts InitClusterOptions) (*clie
} }
func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions) (*client.Client, error) { func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions) (*client.Client, error) {
contextName, err := cli.newContextName(opts.Context) contextName := opts.Context
if err != nil { if contextName == "" {
return nil, err contextName = defaultContextName
}
if _, ok := cli.Config.Contexts[contextName]; ok {
return nil, fmt.Errorf("cluster context '%s' already exists", contextName)
} }
machineClient, err := provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version) machineClient, err := cli.provisionRemoteMachine(ctx, *opts.RemoteMachine, opts.Version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -192,7 +190,7 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
return nil, fmt.Errorf("inspect machine: %w", err) return nil, fmt.Errorf("inspect machine: %w", err)
} }
if minfo.Id != "" { if minfo.Id != "" {
if err = promptResetMachine(ctx, machineClient.MachineClient); err != nil { if err = cli.promptResetMachine(); err != nil {
return nil, err return nil, err
} }
} }
@@ -247,62 +245,30 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
return machineClient, nil return machineClient, nil
} }
// newContextName returns a unique name for a new cluster context. If the provided name is not DefaultContextName,
// and it's already taken, an error is returned. If the name is not provided or is DefaultContextName, the first
// available name "default[-N]" is returned.
func (cli *CLI) newContextName(name string) (string, error) {
if name == "" {
name = DefaultContextName
}
if _, exists := cli.Config.Contexts[name]; !exists {
return name, nil
}
// If non-default context already exists, error out.
if name != DefaultContextName {
return "", fmt.Errorf("cluster context '%s' already exists", name)
}
// The default context already exists, generate a numbered suffix to make it unique.
for i := 1; ; i++ {
name = fmt.Sprintf("%s-%d", DefaultContextName, i)
if _, exists := cli.Config.Contexts[name]; !exists {
return name, nil
}
}
}
type AddMachineOptions struct { type AddMachineOptions struct {
Context string Context string
MachineName string MachineName string
PublicIP *netip.Addr PublicIP *netip.Addr
RemoteMachine *RemoteMachine RemoteMachine RemoteMachine
Version string Version string
} }
// AddMachine provisions a remote machine and adds it to the cluster. It returns a cluster client and a machine client. // AddMachine provisions a remote machine and adds it to the cluster. It returns a client to interact with the machine
// The cluster client is connected to the existing machine in the cluster. It was used to add the new machine to the // which should be closed after use by the caller.
// cluster. The machine client is connected to the new machine and can be used to interact with it. func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client.Client, error) {
// Both client should be closed after use by the caller.
func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client.Client, *client.Client, error) {
contextName := opts.Context contextName := opts.Context
if contextName == "" { if contextName == "" {
contextName = cli.Config.CurrentContext contextName = cli.Config.CurrentContext
} }
c, err := cli.ConnectCluster(ctx, contextName) c, err := cli.ConnectCluster(ctx, contextName)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err) return nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err)
} }
defer func() { defer c.Close()
if err != nil {
c.Close()
}
}()
machineClient, err := provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version) machineClient, err := cli.provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
defer func() { defer func() {
if err != nil { if err != nil {
@@ -313,22 +279,11 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
// Check if the machine is already initialised as a cluster member and prompt the user to reset it first. // Check if the machine is already initialised as a cluster member and prompt the user to reset it first.
minfo, err := machineClient.Inspect(ctx, &emptypb.Empty{}) minfo, err := machineClient.Inspect(ctx, &emptypb.Empty{})
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("inspect machine: %w", err) return nil, fmt.Errorf("inspect machine: %w", err)
} }
if minfo.Id != "" { if minfo.Id != "" {
// Check if the machine is already a member of this cluster. if err = cli.promptResetMachine(); err != nil {
machines, err := c.ListMachines(ctx, nil) return nil, err
if err != nil {
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
}
if slices.ContainsFunc(machines, func(m *pb.MachineMember) bool {
return m.Machine.Id == minfo.Id
}) {
return nil, nil, fmt.Errorf("machine is already a member of this cluster (%s)", minfo.Name)
}
if err = promptResetMachine(ctx, machineClient.MachineClient); err != nil {
return nil, nil, err
} }
} }
@@ -337,19 +292,19 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
// TODO(lhf): remove Unimplemented check when v0.9.0 is released. // TODO(lhf): remove Unimplemented check when v0.9.0 is released.
if err != nil { if err != nil {
if status.Convert(err).Code() != codes.Unimplemented { if status.Convert(err).Code() != codes.Unimplemented {
return nil, nil, fmt.Errorf("check machine prerequisites: %w", err) return nil, fmt.Errorf("check machine prerequisites: %w", err)
} }
} else if !checkResp.Satisfied { } else if !checkResp.Satisfied {
return nil, nil, fmt.Errorf("machine prerequisites not satisfied: %s", checkResp.Error) return nil, fmt.Errorf("machine prerequisites not satisfied: %s", checkResp.Error)
} }
tokenResp, err := machineClient.Token(ctx, &emptypb.Empty{}) tokenResp, err := machineClient.Token(ctx, &emptypb.Empty{})
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("get remote machine token: %w", err) return nil, fmt.Errorf("get remote machine token: %w", err)
} }
token, err := machine.ParseToken(tokenResp.Token) token, err := machine.ParseToken(tokenResp.Token)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("parse remote machine token: %w", err) return nil, fmt.Errorf("parse remote machine token: %w", err)
} }
// Register the machine in the cluster using its public key and endpoints from the token. // Register the machine in the cluster using its public key and endpoints from the token.
@@ -375,13 +330,13 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
addResp, err := c.AddMachine(ctx, addReq) addResp, err := c.AddMachine(ctx, addReq)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err) return nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err)
} }
// Get the most up-to-date list of other machines in the cluster to include them in the join request. // List other machines in the cluster to include them in the join request.
machines, err := c.ListMachines(ctx, nil) machines, err := c.ListMachines(ctx, nil)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("list cluster machines: %w", err) return nil, fmt.Errorf("list cluster machines: %w", err)
} }
otherMachines := make([]*pb.MachineInfo, 0, len(machines)-1) otherMachines := make([]*pb.MachineInfo, 0, len(machines)-1)
for _, m := range machines { for _, m := range machines {
@@ -396,7 +351,7 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
OtherMachines: otherMachines, OtherMachines: otherMachines,
} }
if _, err = machineClient.JoinCluster(ctx, joinReq); err != nil { if _, err = machineClient.JoinCluster(ctx, joinReq); err != nil {
return nil, nil, fmt.Errorf("join cluster: %w", err) return nil, fmt.Errorf("join cluster: %w", err)
} }
// TODO: fix empty context name when using the current context (contextName == ""). // TODO: fix empty context name when using the current context (contextName == "").
@@ -412,29 +367,20 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
} }
cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg) cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg)
if err = cli.Config.Save(); err != nil { if err = cli.Config.Save(); err != nil {
return nil, nil, fmt.Errorf("save config: %w", err) return nil, fmt.Errorf("save config: %w", err)
} }
return c, machineClient, nil return machineClient, nil
} }
// provisionRemoteMachine installs the Uncloud daemon and dependencies on the remote machine over SSH and returns // provisionRemoteMachine installs the Uncloud daemon and dependencies on the remote machine over SSH and returns
// a machine API client to interact with the machine. The client should be closed after use by the caller. // a machine API client to interact with the machine. The client should be closed after use by the caller.
// The version parameter specifies the version of the Uncloud daemon to install. If empty, the latest version is used. // The version parameter specifies the version of the Uncloud daemon to install. If empty, the latest version is used.
// The remoteMachine.SSHKeyPath could be updated to the default SSH key path if it is not set and the SSH agent func (cli *CLI) provisionRemoteMachine(
// authentication fails. ctx context.Context, remoteMachine RemoteMachine, version string,
func provisionRemoteMachine(
ctx context.Context, remoteMachine *RemoteMachine, version string,
) (*client.Client, error) { ) (*client.Client, error) {
// Provision the remote machine by installing the Uncloud daemon and dependencies over SSH. // Provision the remote machine by installing the Uncloud daemon and dependencies over SSH.
sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath) sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath)
// If the SSH connection using SSH agent fails and no key path is provided, try to use the default SSH key.
if err != nil && remoteMachine.KeyPath == "" {
remoteMachine.KeyPath = DefaultSSHKeyPath
sshClient, err = sshexec.Connect(
remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath,
)
}
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"SSH login to remote machine %s: %w", "SSH login to remote machine %s: %w",
@@ -468,6 +414,31 @@ func provisionRemoteMachine(
return machineClient, nil return machineClient, nil
} }
func (cli *CLI) promptResetMachine() error {
var confirm bool
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(
"The remote machine is already initialised as a cluster member. Do you want to reset it first?",
).
Affirmative("Yes!").
Negative("No").
Value(&confirm),
),
).WithAccessible(true)
if err := form.Run(); err != nil {
return fmt.Errorf("prompt user to confirm: %w", err)
}
if !confirm {
return fmt.Errorf("remote machine is already initialised as a cluster member")
}
// TODO: implement resetting the remote machine.
return fmt.Errorf("resetting the remote machine is not implemented yet. " +
"Please manually run 'uncloud-uninstall' on the remote machine to fully uninstall Uncloud from it")
}
// ProgressOut returns an output stream for progress writer. // ProgressOut returns an output stream for progress writer.
func (cli *CLI) ProgressOut() *streams.Out { func (cli *CLI) ProgressOut() *streams.Out {
return streams.NewOut(os.Stdout) return streams.NewOut(os.Stdout)
+2 -6
View File
@@ -53,15 +53,11 @@ func (c *Config) Read() error {
func (c *Config) Save() error { func (c *Config) Save() error {
dir, _ := filepath.Split(c.path) dir, _ := filepath.Split(c.path)
// If dir is empty (e.g., when path is just a filename), use current directory if err := os.MkdirAll(dir, 0700); err != nil {
if dir == "" {
dir = "."
}
if err := os.MkdirAll(dir, 0o700); err != nil {
return fmt.Errorf("create config directory '%s': %w", dir, err) return fmt.Errorf("create config directory '%s': %w", dir, err)
} }
f, err := os.OpenFile(c.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) f, err := os.OpenFile(c.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
return fmt.Errorf("write config file '%s': %w", c.path, err) return fmt.Errorf("write config file '%s': %w", c.path, err)
} }
-80
View File
@@ -1,80 +0,0 @@
package config
import (
"os"
"path/filepath"
"testing"
)
func TestConfig_Save(t *testing.T) {
t.Parallel()
// Create a temporary directory for the test
tmpDir := t.TempDir()
// Change to temp directory so relative paths resolve correctly
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current directory: %v", err)
}
defer func() {
if err := os.Chdir(originalDir); err != nil {
t.Logf("Failed to restore original directory: %v", err)
}
}()
tests := []struct {
name string
configPath string
contextName string
expectFileAt string // Expected file location for verification
useAbsolutePath bool // Whether to use absolute path for expectFileAt
}{
{
name: "relative path without prefix",
configPath: "test-config.yaml",
contextName: "test",
},
{
name: "relative path with prefix",
configPath: "./test-config-2.yaml",
contextName: "test2",
},
{
name: "absolute path",
configPath: filepath.Join(tmpDir, "absolute-config.yaml"),
contextName: "test3",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("Failed to change to temp directory: %v", err)
}
cfg := &Config{
CurrentContext: tt.contextName,
Contexts: map[string]*Context{
tt.contextName: {
Name: tt.contextName,
},
},
path: tt.configPath,
}
// This should not fail when saving the config
err := cfg.Save()
if err != nil {
t.Errorf("Expected no error when saving config, got: %v", err)
}
// Verify the file was created
if _, err := os.Stat(tt.configPath); os.IsNotExist(err) {
t.Errorf("Config file was not created at expected path: %s", tt.configPath)
}
})
}
}
+3 -84
View File
@@ -5,20 +5,12 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/charmbracelet/huh"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/sshexec" "github.com/psviderski/uncloud/internal/sshexec"
"google.golang.org/protobuf/types/known/emptypb"
) )
const ( // TODO: support pinning the script version to the CLI version.
// TODO: support pinning the script version to the CLI version. const installScriptURL = "https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh"
installScriptURL = "https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh"
rootUser = "root"
)
type RemoteMachine struct { type RemoteMachine struct {
User string User string
@@ -32,7 +24,7 @@ func installCmd(user string, version string) string {
var env []string var env []string
// Add the SSH user (non-root) to the uncloud group to allow access to the Uncloud daemon unix socket. // Add the SSH user (non-root) to the uncloud group to allow access to the Uncloud daemon unix socket.
if user != rootUser { if user != "root" {
sudoPrefix = "sudo" sudoPrefix = "sudo"
env = append(env, "UNCLOUD_GROUP_ADD_USER="+sshexec.Quote(user)) env = append(env, "UNCLOUD_GROUP_ADD_USER="+sshexec.Quote(user))
} }
@@ -54,26 +46,6 @@ func provisionMachine(ctx context.Context, exec sshexec.Executor, version string
return fmt.Errorf("run whoami: %w", err) return fmt.Errorf("run whoami: %w", err)
} }
if user != rootUser {
// 'sudo -n' is not used because it fails with 'sudo: a password is required' when the user has no password
// in /etc/shadow even though it may have valid sudo access.
out, err := exec.Run(ctx, "sudo true")
if err != nil {
if strings.Contains(out, "password is required") {
return fmt.Errorf(
"user '%[1]s' requires a password for sudo, but Uncloud needs passwordless sudo or root access "+
"to install and configure the uncloudd daemon on the remote machine.\n\n"+
"Possible solutions:\n"+
"1. Use root user or a user with passwordless sudo instead.\n"+
"2. Configure passwordless sudo for the user '%[1]s' by running on the remote machine:\n"+
" echo '%[1]s ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/%[1]s",
user)
}
return fmt.Errorf("sudo command failed for user '%s': %w. "+
"Please ensure the user has sudo privileges or use root user instead", user, err)
}
}
cmd := installCmd(user, version) cmd := installCmd(user, version)
fmt.Println("Downloading Uncloud install script:", installScriptURL) fmt.Println("Downloading Uncloud install script:", installScriptURL)
@@ -84,56 +56,3 @@ func provisionMachine(ctx context.Context, exec sshexec.Executor, version string
} }
return nil return nil
} }
func promptResetMachine(ctx context.Context, machineClient pb.MachineClient) error {
var confirm bool
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(
"The remote machine is already initialised as a cluster member. Do you want to reset it first?\n" +
"This will:\n" +
"- Remove all service containers from the machine\n" +
"- Reset the machine to the uninitialised state",
).
Affirmative("Yes!").
Negative("No").
Value(&confirm),
),
).WithAccessible(true)
if err := form.Run(); err != nil {
return fmt.Errorf("prompt user to confirm: %w", err)
}
if !confirm {
return fmt.Errorf("remote machine is already initialised as a cluster member")
}
if _, err := machineClient.Reset(ctx, &pb.ResetRequest{}); err != nil {
return fmt.Errorf("reset remote machine: %w. You can also manually run 'uncloud-uninstall' "+
"on the remote machine to fully uninstall Uncloud from it", err)
}
fmt.Println("Resetting the remote machine...")
if err := waitMachineReady(ctx, machineClient, 1*time.Minute); err != nil {
return fmt.Errorf("wait for machine to be ready after reset: %w", err)
}
return nil
}
// waitMachineReady waits for the machine to be ready to serve requests.
func waitMachineReady(ctx context.Context, machineClient pb.MachineClient, timeout time.Duration) error {
boff := backoff.WithContext(backoff.NewExponentialBackOff(
backoff.WithMaxInterval(1*time.Second),
backoff.WithMaxElapsedTime(timeout),
), ctx)
inspect := func() error {
_, err := machineClient.Inspect(ctx, &emptypb.Empty{})
if err != nil {
return fmt.Errorf("inspect machine: %w", err)
}
return nil
}
return backoff.Retry(inspect, boff)
}
+2 -3
View File
@@ -5,15 +5,14 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"github.com/cenkalti/backoff/v4"
"golang.org/x/net/http2"
"log/slog" "log/slog"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"net/url" "net/url"
"time" "time"
"github.com/cenkalti/backoff/v4"
"golang.org/x/net/http2"
) )
const ( const (
+1 -2
View File
@@ -6,12 +6,11 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/cenkalti/backoff/v4"
"io" "io"
"log/slog" "log/slog"
"net/http" "net/http"
"strconv" "strconv"
"github.com/cenkalti/backoff/v4"
) )
type ChangeType string type ChangeType string
+1 -2
View File
@@ -3,9 +3,8 @@ package daemon
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
systemd "github.com/coreos/go-systemd/daemon" systemd "github.com/coreos/go-systemd/daemon"
"log/slog"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
) )
+2 -3
View File
@@ -3,11 +3,10 @@ package daemon
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip"
"os"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/machine/network" "github.com/psviderski/uncloud/internal/machine/network"
"net/netip"
"os"
) )
// MachineToken returns the local machine's token that can be used for adding the machine to a cluster. // MachineToken returns the local machine's token that can be used for adding the machine to a cluster.
+2 -3
View File
@@ -4,11 +4,10 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"time"
"github.com/cenkalti/backoff/v4" "github.com/cenkalti/backoff/v4"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"log/slog"
"time"
) )
// WaitDaemonReady waits for the Docker daemon to start and be ready to serve requests. // WaitDaemonReady waits for the Docker daemon to start and be ready to serve requests.
-176
View File
@@ -1,176 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc v5.27.3
// source: internal/machine/api/pb/caddy.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GetCaddyConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The generated Caddyfile content.
Caddyfile string `protobuf:"bytes,1,opt,name=caddyfile,proto3" json:"caddyfile,omitempty"`
// Timestamp when the config was last modified.
ModifiedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=modified_at,json=modifiedAt,proto3" json:"modified_at,omitempty"`
}
func (x *GetCaddyConfigResponse) Reset() {
*x = GetCaddyConfigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_caddy_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetCaddyConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetCaddyConfigResponse) ProtoMessage() {}
func (x *GetCaddyConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_caddy_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetCaddyConfigResponse.ProtoReflect.Descriptor instead.
func (*GetCaddyConfigResponse) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_caddy_proto_rawDescGZIP(), []int{0}
}
func (x *GetCaddyConfigResponse) GetCaddyfile() string {
if x != nil {
return x.Caddyfile
}
return ""
}
func (x *GetCaddyConfigResponse) GetModifiedAt() *timestamppb.Timestamp {
if x != nil {
return x.ModifiedAt
}
return nil
}
var File_internal_machine_api_pb_caddy_proto protoreflect.FileDescriptor
var file_internal_machine_api_pb_caddy_proto_rawDesc = []byte{
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,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x73, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43,
0x61, 0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x61, 0x64, 0x64, 0x79, 0x66, 0x69, 0x6c, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x61, 0x64, 0x64, 0x79, 0x66, 0x69, 0x6c, 0x65,
0x12, 0x3b, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, 0x32, 0x49, 0x0a,
0x05, 0x43, 0x61, 0x64, 0x64, 0x79, 0x12, 0x40, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b,
0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70,
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_internal_machine_api_pb_caddy_proto_rawDescOnce sync.Once
file_internal_machine_api_pb_caddy_proto_rawDescData = file_internal_machine_api_pb_caddy_proto_rawDesc
)
func file_internal_machine_api_pb_caddy_proto_rawDescGZIP() []byte {
file_internal_machine_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)
})
return file_internal_machine_api_pb_caddy_proto_rawDescData
}
var file_internal_machine_api_pb_caddy_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_internal_machine_api_pb_caddy_proto_goTypes = []any{
(*GetCaddyConfigResponse)(nil), // 0: api.GetCaddyConfigResponse
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
}
var file_internal_machine_api_pb_caddy_proto_depIdxs = []int32{
1, // 0: api.GetCaddyConfigResponse.modified_at:type_name -> google.protobuf.Timestamp
2, // 1: api.Caddy.GetConfig:input_type -> google.protobuf.Empty
0, // 2: api.Caddy.GetConfig:output_type -> api.GetCaddyConfigResponse
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_internal_machine_api_pb_caddy_proto_init() }
func file_internal_machine_api_pb_caddy_proto_init() {
if File_internal_machine_api_pb_caddy_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_internal_machine_api_pb_caddy_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*GetCaddyConfigResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_internal_machine_api_pb_caddy_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_internal_machine_api_pb_caddy_proto_goTypes,
DependencyIndexes: file_internal_machine_api_pb_caddy_proto_depIdxs,
MessageInfos: file_internal_machine_api_pb_caddy_proto_msgTypes,
}.Build()
File_internal_machine_api_pb_caddy_proto = out.File
file_internal_machine_api_pb_caddy_proto_rawDesc = nil
file_internal_machine_api_pb_caddy_proto_goTypes = nil
file_internal_machine_api_pb_caddy_proto_depIdxs = nil
}
-20
View File
@@ -1,20 +0,0 @@
syntax = "proto3";
package api;
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
service Caddy {
// GetConfig retrieves the current Caddy configuration from the machine.
rpc GetConfig(google.protobuf.Empty) returns (GetCaddyConfigResponse);
}
message GetCaddyConfigResponse {
// The generated Caddyfile content.
string caddyfile = 1;
// Timestamp when the config was last modified.
google.protobuf.Timestamp modified_at = 2;
}
-124
View File
@@ -1,124 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.27.3
// source: internal/machine/api/pb/caddy.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 (
Caddy_GetConfig_FullMethodName = "/api.Caddy/GetConfig"
)
// CaddyClient is the client API for Caddy 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.
type CaddyClient interface {
// GetConfig retrieves the current Caddy configuration from the machine.
GetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetCaddyConfigResponse, error)
}
type caddyClient struct {
cc grpc.ClientConnInterface
}
func NewCaddyClient(cc grpc.ClientConnInterface) CaddyClient {
return &caddyClient{cc}
}
func (c *caddyClient) GetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetCaddyConfigResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetCaddyConfigResponse)
err := c.cc.Invoke(ctx, Caddy_GetConfig_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// CaddyServer is the server API for Caddy service.
// All implementations must embed UnimplementedCaddyServer
// for forward compatibility.
type CaddyServer interface {
// GetConfig retrieves the current Caddy configuration from the machine.
GetConfig(context.Context, *emptypb.Empty) (*GetCaddyConfigResponse, error)
mustEmbedUnimplementedCaddyServer()
}
// UnimplementedCaddyServer 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 UnimplementedCaddyServer struct{}
func (UnimplementedCaddyServer) GetConfig(context.Context, *emptypb.Empty) (*GetCaddyConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetConfig not implemented")
}
func (UnimplementedCaddyServer) mustEmbedUnimplementedCaddyServer() {}
func (UnimplementedCaddyServer) testEmbeddedByValue() {}
// UnsafeCaddyServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to CaddyServer will
// result in compilation errors.
type UnsafeCaddyServer interface {
mustEmbedUnimplementedCaddyServer()
}
func RegisterCaddyServer(s grpc.ServiceRegistrar, srv CaddyServer) {
// If the following call pancis, it indicates UnimplementedCaddyServer 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(&Caddy_ServiceDesc, srv)
}
func _Caddy_GetConfig_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.(CaddyServer).GetConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Caddy_GetConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CaddyServer).GetConfig(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
// Caddy_ServiceDesc is the grpc.ServiceDesc for Caddy service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Caddy_ServiceDesc = grpc.ServiceDesc{
ServiceName: "api.Caddy",
HandlerType: (*CaddyServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetConfig",
Handler: _Caddy_GetConfig_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "internal/machine/api/pb/caddy.proto",
}
+107 -350
View File
@@ -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_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{8, 0}
} }
type AddMachineRequest struct { type AddMachineRequest struct {
@@ -339,173 +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 {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
}
func (x *RemoveMachineRequest) Reset() {
*x = RemoveMachineRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RemoveMachineRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveMachineRequest) ProtoMessage() {}
func (x *RemoveMachineRequest) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveMachineRequest.ProtoReflect.Descriptor instead.
func (*RemoveMachineRequest) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{6}
}
func (x *RemoveMachineRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
type Domain struct { type Domain struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -517,7 +350,7 @@ type Domain struct {
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_internal_machine_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)
} }
@@ -530,7 +363,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_internal_machine_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 {
@@ -543,7 +376,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_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{4}
} }
func (x *Domain) GetName() string { func (x *Domain) GetName() string {
@@ -564,7 +397,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_internal_machine_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)
} }
@@ -577,7 +410,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_internal_machine_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 {
@@ -590,7 +423,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_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{5}
} }
func (x *ReserveDomainRequest) GetEndpoint() string { func (x *ReserveDomainRequest) GetEndpoint() string {
@@ -611,7 +444,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_internal_machine_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)
} }
@@ -624,7 +457,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_internal_machine_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 {
@@ -637,7 +470,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_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{6}
} }
func (x *CreateDomainRecordsRequest) GetRecords() []*DNSRecord { func (x *CreateDomainRecordsRequest) GetRecords() []*DNSRecord {
@@ -658,7 +491,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_internal_machine_api_pb_cluster_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -671,7 +504,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_internal_machine_api_pb_cluster_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -684,7 +517,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_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{7}
} }
func (x *CreateDomainRecordsResponse) GetRecords() []*DNSRecord { func (x *CreateDomainRecordsResponse) GetRecords() []*DNSRecord {
@@ -707,7 +540,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_internal_machine_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)
} }
@@ -720,7 +553,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_internal_machine_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 {
@@ -733,7 +566,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_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{8}
} }
func (x *DNSRecord) GetName() string { func (x *DNSRecord) GetName() string {
@@ -797,87 +630,59 @@ var file_internal_machine_api_pb_cluster_proto_rawDesc = []byte{
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63,
0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68,
0x69, 0x6e, 0x65, 0x73, 0x22, 0xbb, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x1c, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x6d, 0x65, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d,
0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e,
0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x46, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71,
0x50, 0x48, 0x01, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x88, 0x01, 0x01, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18,
0x12, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52,
0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x47,
0x52, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65,
0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a,
0x69, 0x70, 0x22, 0x43, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e,
0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x52,
0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x26, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70,
0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79,
0x1c, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x32, 0x0a, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73,
0x14, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x22, 0x2e, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x05, 0x0a, 0x01, 0x41, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02,
0x74, 0x22, 0x46, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x32, 0x86, 0x03, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a,
0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69,
0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68,
0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x4c,
0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53,
0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x41, 0x10,
0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0x92, 0x04, 0x0a, 0x07,
0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x4d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61,
0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0d, 0x55, 0x70, 0x64,
0x61, 0x74, 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,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d,
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65,
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73,
0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30,
0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61,
0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37,
0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d,
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69,
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1f, 0x2e, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f,
0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c,
0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x65, 0x61, 0x73, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x58, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52,
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65,
0x33, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@@ -893,7 +698,7 @@ func file_internal_machine_api_pb_cluster_proto_rawDescGZIP() []byte {
} }
var file_internal_machine_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_internal_machine_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_internal_machine_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_internal_machine_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_internal_machine_api_pb_cluster_proto_goTypes = []any{ var file_internal_machine_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
@@ -901,54 +706,43 @@ var file_internal_machine_api_pb_cluster_proto_goTypes = []any{
(*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 (*Domain)(nil), // 6: api.Domain
(*UpdateMachineResponse)(nil), // 7: api.UpdateMachineResponse (*ReserveDomainRequest)(nil), // 7: api.ReserveDomainRequest
(*RemoveMachineRequest)(nil), // 8: api.RemoveMachineRequest (*CreateDomainRecordsRequest)(nil), // 8: api.CreateDomainRecordsRequest
(*Domain)(nil), // 9: api.Domain (*CreateDomainRecordsResponse)(nil), // 9: api.CreateDomainRecordsResponse
(*ReserveDomainRequest)(nil), // 10: api.ReserveDomainRequest (*DNSRecord)(nil), // 10: api.DNSRecord
(*CreateDomainRecordsRequest)(nil), // 11: api.CreateDomainRecordsRequest (*NetworkConfig)(nil), // 11: api.NetworkConfig
(*CreateDomainRecordsResponse)(nil), // 12: api.CreateDomainRecordsResponse (*IP)(nil), // 12: api.IP
(*DNSRecord)(nil), // 13: api.DNSRecord (*MachineInfo)(nil), // 13: api.MachineInfo
(*NetworkConfig)(nil), // 14: api.NetworkConfig (*emptypb.Empty)(nil), // 14: google.protobuf.Empty
(*IP)(nil), // 15: api.IP
(*MachineInfo)(nil), // 16: api.MachineInfo
(*IPPort)(nil), // 17: api.IPPort
(*emptypb.Empty)(nil), // 18: google.protobuf.Empty
} }
var file_internal_machine_api_pb_cluster_proto_depIdxs = []int32{ var file_internal_machine_api_pb_cluster_proto_depIdxs = []int32{
14, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig 11, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig
15, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP 12, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP
16, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo 13, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo
16, // 3: api.MachineMember.machine:type_name -> api.MachineInfo 13, // 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 10, // 6: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord
17, // 7: api.UpdateMachineRequest.endpoints:type_name -> api.IPPort 10, // 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 14, // 10: api.Cluster.ListMachines:input_type -> google.protobuf.Empty
1, // 11: api.DNSRecord.type:type_name -> api.DNSRecord.RecordType 7, // 11: api.Cluster.ReserveDomain:input_type -> api.ReserveDomainRequest
2, // 12: api.Cluster.AddMachine:input_type -> api.AddMachineRequest 14, // 12: api.Cluster.GetDomain:input_type -> google.protobuf.Empty
18, // 13: api.Cluster.ListMachines:input_type -> google.protobuf.Empty 14, // 13: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty
6, // 14: api.Cluster.UpdateMachine:input_type -> api.UpdateMachineRequest 8, // 14: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest
8, // 15: api.Cluster.RemoveMachine:input_type -> api.RemoveMachineRequest 3, // 15: api.Cluster.AddMachine:output_type -> api.AddMachineResponse
10, // 16: api.Cluster.ReserveDomain:input_type -> api.ReserveDomainRequest 5, // 16: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse
18, // 17: api.Cluster.GetDomain:input_type -> google.protobuf.Empty 6, // 17: api.Cluster.ReserveDomain:output_type -> api.Domain
18, // 18: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty 6, // 18: api.Cluster.GetDomain:output_type -> api.Domain
11, // 19: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest 6, // 19: api.Cluster.ReleaseDomain:output_type -> api.Domain
3, // 20: api.Cluster.AddMachine:output_type -> api.AddMachineResponse 9, // 20: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
5, // 21: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse 15, // [15:21] is the sub-list for method output_type
7, // 22: api.Cluster.UpdateMachine:output_type -> api.UpdateMachineResponse 9, // [9:15] is the sub-list for method input_type
18, // 23: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty 9, // [9:9] is the sub-list for extension type_name
9, // 24: api.Cluster.ReserveDomain:output_type -> api.Domain 9, // [9:9] is the sub-list for extension extendee
9, // 25: api.Cluster.GetDomain:output_type -> api.Domain 0, // [0:9] is the sub-list for field type_name
9, // 26: api.Cluster.ReleaseDomain:output_type -> api.Domain
12, // 27: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
20, // [20:28] is the sub-list for method output_type
12, // [12:20] is the sub-list for method input_type
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_internal_machine_api_pb_cluster_proto_init() }
@@ -1008,42 +802,6 @@ func file_internal_machine_api_pb_cluster_proto_init() {
} }
} }
file_internal_machine_api_pb_cluster_proto_msgTypes[4].Exporter = func(v any, i int) any { file_internal_machine_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 {
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[7].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 +813,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_internal_machine_api_pb_cluster_proto_msgTypes[5].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 +825,7 @@ 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_internal_machine_api_pb_cluster_proto_msgTypes[6].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 +837,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_internal_machine_api_pb_cluster_proto_msgTypes[7].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 +849,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_internal_machine_api_pb_cluster_proto_msgTypes[8].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,14 +862,13 @@ func file_internal_machine_api_pb_cluster_proto_init() {
} }
} }
} }
file_internal_machine_api_pb_cluster_proto_msgTypes[4].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_internal_machine_api_pb_cluster_proto_rawDesc,
NumEnums: 2, NumEnums: 2,
NumMessages: 12, NumMessages: 9,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
-20
View File
@@ -11,8 +11,6 @@ import "internal/machine/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 ReserveDomain(ReserveDomainRequest) returns (Domain); rpc ReserveDomain(ReserveDomainRequest) returns (Domain);
rpc GetDomain(google.protobuf.Empty) returns (Domain); rpc GetDomain(google.protobuf.Empty) returns (Domain);
@@ -51,24 +49,6 @@ 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 {
string id = 1;
}
message Domain { message Domain {
string name = 1; string name = 1;
} }
@@ -22,8 +22,6 @@ 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_ReserveDomain_FullMethodName = "/api.Cluster/ReserveDomain" Cluster_ReserveDomain_FullMethodName = "/api.Cluster/ReserveDomain"
Cluster_GetDomain_FullMethodName = "/api.Cluster/GetDomain" Cluster_GetDomain_FullMethodName = "/api.Cluster/GetDomain"
Cluster_ReleaseDomain_FullMethodName = "/api.Cluster/ReleaseDomain" Cluster_ReleaseDomain_FullMethodName = "/api.Cluster/ReleaseDomain"
@@ -36,8 +34,6 @@ 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)
ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error) ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, 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)
@@ -72,26 +68,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) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Cluster_RemoveMachine_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *clusterClient) ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error) { func (c *clusterClient) ReserveDomain(ctx context.Context, in *ReserveDomainRequest, 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,8 +114,6 @@ 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)
ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, 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)
@@ -160,12 +134,6 @@ 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) {
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")
} }
@@ -235,42 +203,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) {
in := new(RemoveMachineRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ClusterServer).RemoveMachine(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Cluster_RemoveMachine_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ClusterServer).RemoveMachine(ctx, req.(*RemoveMachineRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Cluster_ReserveDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _Cluster_ReserveDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReserveDomainRequest) in := new(ReserveDomainRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -358,14 +290,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",
Handler: _Cluster_RemoveMachine_Handler,
},
{ {
MethodName: "ReserveDomain", MethodName: "ReserveDomain",
Handler: _Cluster_ReserveDomain_Handler, Handler: _Cluster_ReserveDomain_Handler,
+155 -212
View File
@@ -21,6 +21,63 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
type CheckPrerequisitesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Overall status of the checks.
Satisfied bool `protobuf:"varint,1,opt,name=satisfied,proto3" json:"satisfied,omitempty"`
// Error message if not satisfied (empty if all checks pass).
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *CheckPrerequisitesResponse) Reset() {
*x = CheckPrerequisitesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CheckPrerequisitesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckPrerequisitesResponse) ProtoMessage() {}
func (x *CheckPrerequisitesResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_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 CheckPrerequisitesResponse.ProtoReflect.Descriptor instead.
func (*CheckPrerequisitesResponse) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{0}
}
func (x *CheckPrerequisitesResponse) GetSatisfied() bool {
if x != nil {
return x.Satisfied
}
return false
}
func (x *CheckPrerequisitesResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type MachineInfo struct { type MachineInfo struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -35,7 +92,7 @@ type MachineInfo struct {
func (x *MachineInfo) Reset() { func (x *MachineInfo) Reset() {
*x = MachineInfo{} *x = MachineInfo{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[0] mi := &file_internal_machine_api_pb_machine_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -48,7 +105,7 @@ func (x *MachineInfo) String() string {
func (*MachineInfo) ProtoMessage() {} func (*MachineInfo) ProtoMessage() {}
func (x *MachineInfo) ProtoReflect() protoreflect.Message { func (x *MachineInfo) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[0] mi := &file_internal_machine_api_pb_machine_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 {
@@ -61,7 +118,7 @@ func (x *MachineInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use MachineInfo.ProtoReflect.Descriptor instead. // Deprecated: Use MachineInfo.ProtoReflect.Descriptor instead.
func (*MachineInfo) Descriptor() ([]byte, []int) { func (*MachineInfo) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{0} return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{1}
} }
func (x *MachineInfo) GetId() string { func (x *MachineInfo) GetId() string {
@@ -106,7 +163,7 @@ type NetworkConfig struct {
func (x *NetworkConfig) Reset() { func (x *NetworkConfig) Reset() {
*x = NetworkConfig{} *x = NetworkConfig{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[1] mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -119,7 +176,7 @@ func (x *NetworkConfig) String() string {
func (*NetworkConfig) ProtoMessage() {} func (*NetworkConfig) ProtoMessage() {}
func (x *NetworkConfig) ProtoReflect() protoreflect.Message { func (x *NetworkConfig) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[1] mi := &file_internal_machine_api_pb_machine_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 {
@@ -132,7 +189,7 @@ func (x *NetworkConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use NetworkConfig.ProtoReflect.Descriptor instead. // Deprecated: Use NetworkConfig.ProtoReflect.Descriptor instead.
func (*NetworkConfig) Descriptor() ([]byte, []int) { func (*NetworkConfig) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{1} return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{2}
} }
func (x *NetworkConfig) GetSubnet() *IPPrefix { func (x *NetworkConfig) GetSubnet() *IPPrefix {
@@ -163,63 +220,6 @@ func (x *NetworkConfig) GetPublicKey() []byte {
return nil return nil
} }
type CheckPrerequisitesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Overall status of the checks.
Satisfied bool `protobuf:"varint,1,opt,name=satisfied,proto3" json:"satisfied,omitempty"`
// Error message if not satisfied (empty if all checks pass).
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *CheckPrerequisitesResponse) Reset() {
*x = CheckPrerequisitesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CheckPrerequisitesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckPrerequisitesResponse) ProtoMessage() {}
func (x *CheckPrerequisitesResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_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 CheckPrerequisitesResponse.ProtoReflect.Descriptor instead.
func (*CheckPrerequisitesResponse) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{2}
}
func (x *CheckPrerequisitesResponse) GetSatisfied() bool {
if x != nil {
return x.Satisfied
}
return false
}
func (x *CheckPrerequisitesResponse) GetError() string {
if x != nil {
return x.Error
}
return ""
}
type InitClusterRequest struct { type InitClusterRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -466,44 +466,6 @@ func (x *TokenResponse) GetToken() string {
return "" return ""
} }
type ResetRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ResetRequest) Reset() {
*x = ResetRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResetRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResetRequest) ProtoMessage() {}
func (x *ResetRequest) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_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 ResetRequest.ProtoReflect.Descriptor instead.
func (*ResetRequest) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{7}
}
type Service struct { type Service struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -518,7 +480,7 @@ type Service struct {
func (x *Service) Reset() { func (x *Service) Reset() {
*x = Service{} *x = Service{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[8] mi := &file_internal_machine_api_pb_machine_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -531,7 +493,7 @@ func (x *Service) String() string {
func (*Service) ProtoMessage() {} func (*Service) ProtoMessage() {}
func (x *Service) ProtoReflect() protoreflect.Message { func (x *Service) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[8] mi := &file_internal_machine_api_pb_machine_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -544,7 +506,7 @@ func (x *Service) ProtoReflect() protoreflect.Message {
// Deprecated: Use Service.ProtoReflect.Descriptor instead. // Deprecated: Use Service.ProtoReflect.Descriptor instead.
func (*Service) Descriptor() ([]byte, []int) { func (*Service) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{8} return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{7}
} }
func (x *Service) GetId() string { func (x *Service) GetId() string {
@@ -586,7 +548,7 @@ type InspectServiceRequest struct {
func (x *InspectServiceRequest) Reset() { func (x *InspectServiceRequest) Reset() {
*x = InspectServiceRequest{} *x = InspectServiceRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[9] mi := &file_internal_machine_api_pb_machine_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -599,7 +561,7 @@ func (x *InspectServiceRequest) String() string {
func (*InspectServiceRequest) ProtoMessage() {} func (*InspectServiceRequest) ProtoMessage() {}
func (x *InspectServiceRequest) ProtoReflect() protoreflect.Message { func (x *InspectServiceRequest) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[9] mi := &file_internal_machine_api_pb_machine_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 {
@@ -612,7 +574,7 @@ func (x *InspectServiceRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use InspectServiceRequest.ProtoReflect.Descriptor instead. // Deprecated: Use InspectServiceRequest.ProtoReflect.Descriptor instead.
func (*InspectServiceRequest) Descriptor() ([]byte, []int) { func (*InspectServiceRequest) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{9} return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{8}
} }
func (x *InspectServiceRequest) GetId() string { func (x *InspectServiceRequest) GetId() string {
@@ -633,7 +595,7 @@ type InspectServiceResponse struct {
func (x *InspectServiceResponse) Reset() { func (x *InspectServiceResponse) Reset() {
*x = InspectServiceResponse{} *x = InspectServiceResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[10] mi := &file_internal_machine_api_pb_machine_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -646,7 +608,7 @@ func (x *InspectServiceResponse) String() string {
func (*InspectServiceResponse) ProtoMessage() {} func (*InspectServiceResponse) ProtoMessage() {}
func (x *InspectServiceResponse) ProtoReflect() protoreflect.Message { func (x *InspectServiceResponse) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[10] mi := &file_internal_machine_api_pb_machine_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 {
@@ -659,7 +621,7 @@ func (x *InspectServiceResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use InspectServiceResponse.ProtoReflect.Descriptor instead. // Deprecated: Use InspectServiceResponse.ProtoReflect.Descriptor instead.
func (*InspectServiceResponse) Descriptor() ([]byte, []int) { func (*InspectServiceResponse) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{10} return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{9}
} }
func (x *InspectServiceResponse) GetService() *Service { func (x *InspectServiceResponse) GetService() *Service {
@@ -682,7 +644,7 @@ type Service_Container struct {
func (x *Service_Container) Reset() { func (x *Service_Container) Reset() {
*x = Service_Container{} *x = Service_Container{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[11] mi := &file_internal_machine_api_pb_machine_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -695,7 +657,7 @@ func (x *Service_Container) String() string {
func (*Service_Container) ProtoMessage() {} func (*Service_Container) ProtoMessage() {}
func (x *Service_Container) ProtoReflect() protoreflect.Message { func (x *Service_Container) ProtoReflect() protoreflect.Message {
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[11] mi := &file_internal_machine_api_pb_machine_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 {
@@ -708,7 +670,7 @@ func (x *Service_Container) ProtoReflect() protoreflect.Message {
// Deprecated: Use Service_Container.ProtoReflect.Descriptor instead. // Deprecated: Use Service_Container.ProtoReflect.Descriptor instead.
func (*Service_Container) Descriptor() ([]byte, []int) { func (*Service_Container) Descriptor() ([]byte, []int) {
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{8, 0} return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{7, 0}
} }
func (x *Service_Container) GetMachineId() string { func (x *Service_Container) GetMachineId() string {
@@ -735,31 +697,31 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0x85, 0x01, 0x0a, 0x0b, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x50, 0x0a, 0x1a, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x73, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x09, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03, 0x52, 0x09, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x69, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x72, 0x22, 0x85, 0x01, 0x0a, 0x0b, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66,
0x6b, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x04, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x08, 0x70, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x22, 0xae, 0x01, 0x0a, 0x0d, 0x4e, 0x65, 0x74, 0x77, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x75, 0x62, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74,
0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77,
0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x52, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x6f, 0x72, 0x6b, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70,
0x12, 0x2c, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52,
0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x22, 0xae, 0x01, 0x0a, 0x0d, 0x4e, 0x65,
0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x29, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x06, 0x73,
0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70,
0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x09, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x52, 0x06, 0x73, 0x75, 0x62, 0x6e,
0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x5f, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x50, 0x0a, 0x1a, 0x43, 0x68, 0x65, 0x63, 0x49, 0x50, 0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x70,
0x6b, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x12, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74,
0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x61, 0x74, 0x69, 0x73, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70,
0x66, 0x69, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xc3, 0x01, 0x0a, 0x12, 0x49, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xc3, 0x01, 0x0a, 0x12, 0x49,
0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e,
@@ -786,8 +748,7 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x25, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x25,
0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x0e, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x65, 0x74, 0x52, 0x65, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xc3, 0x01, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xc3, 0x01, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20,
@@ -806,7 +767,7 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26,
0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x73, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x32, 0xc3, 0x03, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x32, 0x8f, 0x03, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x68, 0x69,
0x6e, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72, 0x65, 0x6e, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72, 0x65,
0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x71, 0x75, 0x69, 0x73, 0x69, 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, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
@@ -826,19 +787,16 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x70,
0x65, 0x63, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x65, 0x63, 0x74, 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, 0x10, 0x2e, 0x61, 0x70, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x32, 0x0a, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x49, 0x0a,
0x05, 0x52, 0x65, 0x73, 0x65, 0x74, 0x12, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x0e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70,
0x79, 0x12, 0x49, 0x0a, 0x0e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x69, 0x63, 0x65, 0x12, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68,
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b,
0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@@ -853,54 +811,51 @@ func file_internal_machine_api_pb_machine_proto_rawDescGZIP() []byte {
return file_internal_machine_api_pb_machine_proto_rawDescData return file_internal_machine_api_pb_machine_proto_rawDescData
} }
var file_internal_machine_api_pb_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_internal_machine_api_pb_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_internal_machine_api_pb_machine_proto_goTypes = []any{ var file_internal_machine_api_pb_machine_proto_goTypes = []any{
(*MachineInfo)(nil), // 0: api.MachineInfo (*CheckPrerequisitesResponse)(nil), // 0: api.CheckPrerequisitesResponse
(*NetworkConfig)(nil), // 1: api.NetworkConfig (*MachineInfo)(nil), // 1: api.MachineInfo
(*CheckPrerequisitesResponse)(nil), // 2: api.CheckPrerequisitesResponse (*NetworkConfig)(nil), // 2: api.NetworkConfig
(*InitClusterRequest)(nil), // 3: api.InitClusterRequest (*InitClusterRequest)(nil), // 3: api.InitClusterRequest
(*InitClusterResponse)(nil), // 4: api.InitClusterResponse (*InitClusterResponse)(nil), // 4: api.InitClusterResponse
(*JoinClusterRequest)(nil), // 5: api.JoinClusterRequest (*JoinClusterRequest)(nil), // 5: api.JoinClusterRequest
(*TokenResponse)(nil), // 6: api.TokenResponse (*TokenResponse)(nil), // 6: api.TokenResponse
(*ResetRequest)(nil), // 7: api.ResetRequest (*Service)(nil), // 7: api.Service
(*Service)(nil), // 8: api.Service (*InspectServiceRequest)(nil), // 8: api.InspectServiceRequest
(*InspectServiceRequest)(nil), // 9: api.InspectServiceRequest (*InspectServiceResponse)(nil), // 9: api.InspectServiceResponse
(*InspectServiceResponse)(nil), // 10: api.InspectServiceResponse (*Service_Container)(nil), // 10: api.Service.Container
(*Service_Container)(nil), // 11: api.Service.Container (*IP)(nil), // 11: api.IP
(*IP)(nil), // 12: api.IP (*IPPrefix)(nil), // 12: api.IPPrefix
(*IPPrefix)(nil), // 13: api.IPPrefix (*IPPort)(nil), // 13: api.IPPort
(*IPPort)(nil), // 14: api.IPPort (*emptypb.Empty)(nil), // 14: google.protobuf.Empty
(*emptypb.Empty)(nil), // 15: google.protobuf.Empty
} }
var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{ var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{
1, // 0: api.MachineInfo.network:type_name -> api.NetworkConfig 2, // 0: api.MachineInfo.network:type_name -> api.NetworkConfig
12, // 1: api.MachineInfo.public_ip:type_name -> api.IP 11, // 1: api.MachineInfo.public_ip:type_name -> api.IP
13, // 2: api.NetworkConfig.subnet:type_name -> api.IPPrefix 12, // 2: api.NetworkConfig.subnet:type_name -> api.IPPrefix
12, // 3: api.NetworkConfig.management_ip:type_name -> api.IP 11, // 3: api.NetworkConfig.management_ip:type_name -> api.IP
14, // 4: api.NetworkConfig.endpoints:type_name -> api.IPPort 13, // 4: api.NetworkConfig.endpoints:type_name -> api.IPPort
13, // 5: api.InitClusterRequest.network:type_name -> api.IPPrefix 12, // 5: api.InitClusterRequest.network:type_name -> api.IPPrefix
12, // 6: api.InitClusterRequest.public_ip:type_name -> api.IP 11, // 6: api.InitClusterRequest.public_ip:type_name -> api.IP
0, // 7: api.InitClusterResponse.machine:type_name -> api.MachineInfo 1, // 7: api.InitClusterResponse.machine:type_name -> api.MachineInfo
0, // 8: api.JoinClusterRequest.machine:type_name -> api.MachineInfo 1, // 8: api.JoinClusterRequest.machine:type_name -> api.MachineInfo
0, // 9: api.JoinClusterRequest.other_machines:type_name -> api.MachineInfo 1, // 9: api.JoinClusterRequest.other_machines:type_name -> api.MachineInfo
11, // 10: api.Service.containers:type_name -> api.Service.Container 10, // 10: api.Service.containers:type_name -> api.Service.Container
8, // 11: api.InspectServiceResponse.service:type_name -> api.Service 7, // 11: api.InspectServiceResponse.service:type_name -> api.Service
15, // 12: api.Machine.CheckPrerequisites:input_type -> google.protobuf.Empty 14, // 12: api.Machine.CheckPrerequisites:input_type -> google.protobuf.Empty
3, // 13: api.Machine.InitCluster:input_type -> api.InitClusterRequest 3, // 13: api.Machine.InitCluster:input_type -> api.InitClusterRequest
5, // 14: api.Machine.JoinCluster:input_type -> api.JoinClusterRequest 5, // 14: api.Machine.JoinCluster:input_type -> api.JoinClusterRequest
15, // 15: api.Machine.Token:input_type -> google.protobuf.Empty 14, // 15: api.Machine.Token:input_type -> google.protobuf.Empty
15, // 16: api.Machine.Inspect:input_type -> google.protobuf.Empty 14, // 16: api.Machine.Inspect:input_type -> google.protobuf.Empty
7, // 17: api.Machine.Reset:input_type -> api.ResetRequest 8, // 17: api.Machine.InspectService:input_type -> api.InspectServiceRequest
9, // 18: api.Machine.InspectService:input_type -> api.InspectServiceRequest 0, // 18: api.Machine.CheckPrerequisites:output_type -> api.CheckPrerequisitesResponse
2, // 19: api.Machine.CheckPrerequisites:output_type -> api.CheckPrerequisitesResponse 4, // 19: api.Machine.InitCluster:output_type -> api.InitClusterResponse
4, // 20: api.Machine.InitCluster:output_type -> api.InitClusterResponse 14, // 20: api.Machine.JoinCluster:output_type -> google.protobuf.Empty
15, // 21: api.Machine.JoinCluster:output_type -> google.protobuf.Empty 6, // 21: api.Machine.Token:output_type -> api.TokenResponse
6, // 22: api.Machine.Token:output_type -> api.TokenResponse 1, // 22: api.Machine.Inspect:output_type -> api.MachineInfo
0, // 23: api.Machine.Inspect:output_type -> api.MachineInfo 9, // 23: api.Machine.InspectService:output_type -> api.InspectServiceResponse
15, // 24: api.Machine.Reset:output_type -> google.protobuf.Empty 18, // [18:24] is the sub-list for method output_type
10, // 25: api.Machine.InspectService:output_type -> api.InspectServiceResponse 12, // [12:18] is the sub-list for method input_type
19, // [19:26] is the sub-list for method output_type
12, // [12:19] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name 12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee 12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name 0, // [0:12] is the sub-list for field type_name
@@ -914,7 +869,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
file_internal_machine_api_pb_common_proto_init() file_internal_machine_api_pb_common_proto_init()
if !protoimpl.UnsafeEnabled { if !protoimpl.UnsafeEnabled {
file_internal_machine_api_pb_machine_proto_msgTypes[0].Exporter = func(v any, i int) any { file_internal_machine_api_pb_machine_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*MachineInfo); i { switch v := v.(*CheckPrerequisitesResponse); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -926,7 +881,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
} }
} }
file_internal_machine_api_pb_machine_proto_msgTypes[1].Exporter = func(v any, i int) any { file_internal_machine_api_pb_machine_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*NetworkConfig); i { switch v := v.(*MachineInfo); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -938,7 +893,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
} }
} }
file_internal_machine_api_pb_machine_proto_msgTypes[2].Exporter = func(v any, i int) any { file_internal_machine_api_pb_machine_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*CheckPrerequisitesResponse); i { switch v := v.(*NetworkConfig); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@@ -998,18 +953,6 @@ func file_internal_machine_api_pb_machine_proto_init() {
} }
} }
file_internal_machine_api_pb_machine_proto_msgTypes[7].Exporter = func(v any, i int) any { file_internal_machine_api_pb_machine_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*ResetRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_internal_machine_api_pb_machine_proto_msgTypes[8].Exporter = func(v any, i int) any {
switch v := v.(*Service); i { switch v := v.(*Service); i {
case 0: case 0:
return &v.state return &v.state
@@ -1021,7 +964,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
return nil return nil
} }
} }
file_internal_machine_api_pb_machine_proto_msgTypes[9].Exporter = func(v any, i int) any { file_internal_machine_api_pb_machine_proto_msgTypes[8].Exporter = func(v any, i int) any {
switch v := v.(*InspectServiceRequest); i { switch v := v.(*InspectServiceRequest); i {
case 0: case 0:
return &v.state return &v.state
@@ -1033,7 +976,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
return nil return nil
} }
} }
file_internal_machine_api_pb_machine_proto_msgTypes[10].Exporter = func(v any, i int) any { file_internal_machine_api_pb_machine_proto_msgTypes[9].Exporter = func(v any, i int) any {
switch v := v.(*InspectServiceResponse); i { switch v := v.(*InspectServiceResponse); i {
case 0: case 0:
return &v.state return &v.state
@@ -1045,7 +988,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
return nil return nil
} }
} }
file_internal_machine_api_pb_machine_proto_msgTypes[11].Exporter = func(v any, i int) any { file_internal_machine_api_pb_machine_proto_msgTypes[10].Exporter = func(v any, i int) any {
switch v := v.(*Service_Container); i { switch v := v.(*Service_Container); i {
case 0: case 0:
return &v.state return &v.state
@@ -1068,7 +1011,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_internal_machine_api_pb_machine_proto_rawDesc, RawDescriptor: file_internal_machine_api_pb_machine_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 12, NumMessages: 11,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },
-6
View File
@@ -14,9 +14,6 @@ service Machine {
rpc JoinCluster(JoinClusterRequest) returns (google.protobuf.Empty); rpc JoinCluster(JoinClusterRequest) returns (google.protobuf.Empty);
rpc Token(google.protobuf.Empty) returns (TokenResponse); rpc Token(google.protobuf.Empty) returns (TokenResponse);
rpc Inspect(google.protobuf.Empty) returns (MachineInfo); rpc Inspect(google.protobuf.Empty) returns (MachineInfo);
// 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 InspectService(InspectServiceRequest) returns (InspectServiceResponse);
} }
@@ -64,9 +61,6 @@ message TokenResponse {
string token = 1; string token = 1;
} }
message ResetRequest {
}
message Service { message Service {
string id = 1; string id = 1;
string name = 2; string name = 2;
@@ -25,7 +25,6 @@ const (
Machine_JoinCluster_FullMethodName = "/api.Machine/JoinCluster" Machine_JoinCluster_FullMethodName = "/api.Machine/JoinCluster"
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_Reset_FullMethodName = "/api.Machine/Reset"
Machine_InspectService_FullMethodName = "/api.Machine/InspectService" Machine_InspectService_FullMethodName = "/api.Machine/InspectService"
) )
@@ -33,14 +32,11 @@ const (
// //
// 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. // 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.
type MachineClient interface { type MachineClient interface {
// CheckPrerequisites verifies if the machine meets all necessary system requirements to participate in the cluster.
CheckPrerequisites(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CheckPrerequisitesResponse, error) CheckPrerequisites(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CheckPrerequisitesResponse, error)
InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error) InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error)
JoinCluster(ctx context.Context, in *JoinClusterRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) JoinCluster(ctx context.Context, in *JoinClusterRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
Token(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TokenResponse, error) Token(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TokenResponse, error)
Inspect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MachineInfo, error) Inspect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MachineInfo, error)
// 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)
InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error) InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error)
} }
@@ -102,16 +98,6 @@ func (c *machineClient) Inspect(ctx context.Context, in *emptypb.Empty, opts ...
return out, nil return out, nil
} }
func (c *machineClient) Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Machine_Reset_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *machineClient) InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error) { func (c *machineClient) InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(InspectServiceResponse) out := new(InspectServiceResponse)
@@ -126,14 +112,11 @@ func (c *machineClient) InspectService(ctx context.Context, in *InspectServiceRe
// All implementations must embed UnimplementedMachineServer // All implementations must embed UnimplementedMachineServer
// for forward compatibility. // for forward compatibility.
type MachineServer interface { type MachineServer interface {
// CheckPrerequisites verifies if the machine meets all necessary system requirements to participate in the cluster.
CheckPrerequisites(context.Context, *emptypb.Empty) (*CheckPrerequisitesResponse, error) CheckPrerequisites(context.Context, *emptypb.Empty) (*CheckPrerequisitesResponse, error)
InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error) InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error)
JoinCluster(context.Context, *JoinClusterRequest) (*emptypb.Empty, error) JoinCluster(context.Context, *JoinClusterRequest) (*emptypb.Empty, error)
Token(context.Context, *emptypb.Empty) (*TokenResponse, error) Token(context.Context, *emptypb.Empty) (*TokenResponse, error)
Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error) Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error)
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
Reset(context.Context, *ResetRequest) (*emptypb.Empty, error)
InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error) InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error)
mustEmbedUnimplementedMachineServer() mustEmbedUnimplementedMachineServer()
} }
@@ -160,9 +143,6 @@ func (UnimplementedMachineServer) Token(context.Context, *emptypb.Empty) (*Token
func (UnimplementedMachineServer) Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error) { func (UnimplementedMachineServer) Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method Inspect not implemented") return nil, status.Errorf(codes.Unimplemented, "method Inspect not implemented")
} }
func (UnimplementedMachineServer) Reset(context.Context, *ResetRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Reset not implemented")
}
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")
} }
@@ -277,24 +257,6 @@ func _Machine_Inspect_Handler(srv interface{}, ctx context.Context, dec func(int
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _Machine_Reset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MachineServer).Reset(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Machine_Reset_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MachineServer).Reset(ctx, req.(*ResetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Machine_InspectService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _Machine_InspectService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InspectServiceRequest) in := new(InspectServiceRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -340,10 +302,6 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
MethodName: "Inspect", MethodName: "Inspect",
Handler: _Machine_Inspect_Handler, Handler: _Machine_Inspect_Handler,
}, },
{
MethodName: "Reset",
Handler: _Machine_Reset_Handler,
},
{ {
MethodName: "InspectService", MethodName: "InspectService",
Handler: _Machine_InspectService_Handler, Handler: _Machine_InspectService_Handler,
+1 -2
View File
@@ -2,11 +2,10 @@ package proxy
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/encoding/protowire"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"github.com/psviderski/uncloud/internal/machine/api/pb"
) )
// One2ManyResponder converts upstream responses into messages from upstreams, so that multiple // One2ManyResponder converts upstream responses into messages from upstreams, so that multiple
+1 -2
View File
@@ -2,12 +2,11 @@ package proxy
import ( import (
"context" "context"
"sync"
"github.com/siderolabs/grpc-proxy/proxy" "github.com/siderolabs/grpc-proxy/proxy"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"sync"
) )
// Director manages routing of gRPC requests between local and remote backends. // Director manages routing of gRPC requests between local and remote backends.
+1 -2
View File
@@ -2,12 +2,11 @@ package proxy
import ( import (
"context" "context"
"sync"
"github.com/siderolabs/grpc-proxy/proxy" "github.com/siderolabs/grpc-proxy/proxy"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"sync"
) )
// LocalBackend is a proxy.One2ManyResponder implementation that proxies to a local gRPC server listening on a Unix socket. // LocalBackend is a proxy.One2ManyResponder implementation that proxies to a local gRPC server listening on a Unix socket.
+3 -4
View File
@@ -3,15 +3,14 @@ package proxy
import ( import (
"context" "context"
"fmt" "fmt"
"net/netip"
"sync"
"time"
"github.com/siderolabs/grpc-proxy/proxy" "github.com/siderolabs/grpc-proxy/proxy"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/backoff" "google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"net/netip"
"sync"
"time"
) )
// RemoteBackend is a proxy.One2ManyResponder implementation that proxies to a remote gRPC server, injecting machine metadata // RemoteBackend is a proxy.One2ManyResponder implementation that proxies to a remote gRPC server, injecting machine metadata
-342
View File
@@ -1,342 +0,0 @@
package caddyconfig
import (
"bytes"
"cmp"
"context"
"fmt"
"log/slog"
"maps"
"net"
"slices"
"strconv"
"strings"
"text/template"
"github.com/psviderski/uncloud/internal/machine/store"
"github.com/psviderski/uncloud/pkg/api"
)
const (
caddyfileHeader = `# This file is autogenerated by Uncloud based on the configuration of running services.
# Do not edit manually. Any manual changes will be overwritten on the next update.
`
caddyfileTemplate = `# Health check endpoint to verify Caddy reachability on this machine.
http:// {
handle {{.VerifyPath}} {
respond "{{.VerifyResponse}}" 200
}
log
}
(common_proxy) {
# Retry failed requests up to lb_retries times against other available upstreams.
lb_retries 3
# Upstreams are marked unhealthy for fail_duration after a failed request (passive health checking).
fail_duration 30s
}
{{- if or .HTTPHostUpstreams .HTTPSHostUpstreams }}
# Sites generated from service ports.{{end}}
{{- range $hostname, $upstreams := .HTTPHostUpstreams}}
http://{{$hostname}} {
reverse_proxy {{join $upstreams " "}} {
import common_proxy
}
log
}{{end}}
{{- range $hostname, $upstreams := .HTTPSHostUpstreams}}
https://{{$hostname}} {
reverse_proxy {{join $upstreams " "}} {
import common_proxy
}
log
}{{end}}
`
caddyfileUnavailabeFooter = `# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine
# or the latest generated config is invalid. Please check the Caddy logs if it's running.
`
)
// CaddyfileGenerator generates a Caddyfile configuration for the Caddy reverse proxy.
type CaddyfileGenerator struct {
// machineID is the unique identifier of the machine where the controller is running.
machineID string
validator CaddyfileValidator
log *slog.Logger
}
// CaddyfileValidator is an interface for validating Caddyfile configurations.
type CaddyfileValidator interface {
Validate(ctx context.Context, caddyfile string) error
}
func NewCaddyfileGenerator(machineID string, validator CaddyfileValidator, log *slog.Logger) *CaddyfileGenerator {
if log == nil {
log = slog.Default()
}
return &CaddyfileGenerator{
machineID: machineID,
validator: validator,
log: log,
}
}
// Generate creates a Caddyfile configuration based on the provided service containers.
// The Caddyfile is generated from the service ports of the healthy containers.
// If a 'caddy' service container is running on this machine and defines a custom Caddy config (x-caddy) in its service
// spec, it will be validated and prepended to the generated Caddyfile. Custom Caddy configs (x-caddy) defined in other
// service specs are validated and appended to the generated Caddyfile. Invalid configs are logged and skipped to ensure
// the generated Caddyfile remains valid.
//
// The final Caddyfile structure includes:
//
// [caddy x-caddy (global config)]
// [generated Caddyfile from all service ports]
// [service-a x-caddy]
// ...
// [service-z x-caddy]
//
// If includeCustom is false, custom Caddy configs (x-caddy) are not included in the generated Caddyfile.
func (g *CaddyfileGenerator) Generate(
ctx context.Context, records []store.ContainerRecord, includeCustom bool,
) (string, error) {
containers := make([]api.ServiceContainer, len(records))
for i, cr := range records {
containers[i] = cr.Container
}
// Sort containers by service name and creation time to generate a stable Caddyfile.
slices.SortStableFunc(containers, func(a, b api.ServiceContainer) int {
return cmp.Or(
strings.Compare(a.ServiceName(), b.ServiceName()),
a.CreatedTime().Compare(b.CreatedTime()),
)
})
caddyfile, err := g.generateBaseFromPorts(containers)
if err != nil {
return "", fmt.Errorf("generate base Caddyfile from service ports: %w", err)
}
if !includeCustom {
return fmt.Sprintf("%s\n%s\n%s", caddyfileHeader, caddyfile, caddyfileUnavailabeFooter), nil
}
upstreams := serviceUpstreams(containers)
// Track validation errors for reporting.
var configErrors []string
// Find the 'caddy' service container on this machine. Use the most recent one if multiple exist.
var caddyCtr *api.ServiceContainer
for _, cr := range records {
if cr.MachineID == g.machineID && cr.Container.ServiceName() == CaddyServiceName &&
(caddyCtr == nil || cr.Container.CreatedTime().Compare(caddyCtr.CreatedTime()) > 0) {
caddyCtr = &cr.Container
}
}
// If the caddy container is running on this machine and has a custom Caddy config (global),
// prepend it to the generated Caddyfile and validate it.
if caddyCtr != nil && caddyCtr.ServiceSpec.CaddyConfig() != "" {
// Render the custom global Caddy config as a Go template with the upstreams.
tmplCtx := templateContext{
Name: caddyCtr.ServiceName(),
Upstreams: upstreams,
}
renderedConfig, err := renderCaddyfile(tmplCtx, caddyCtr.ServiceSpec.CaddyConfig())
if err != nil {
g.log.Error("Failed to render template directives in user-defined global Caddy config, skipping it.",
"service", caddyCtr.ServiceName(), "container", caddyCtr.ID, "err", err)
configErrors = append(configErrors,
fmt.Sprintf("service '%s': failed to render template: %v", caddyCtr.ServiceName(), err))
} else {
caddyfileCandidate := fmt.Sprintf("# User-defined global config from service '%s'.\n%s\n\n%s",
caddyCtr.ServiceName(), renderedConfig, caddyfile)
if err = g.validator.Validate(ctx, caddyfileCandidate); err != nil {
g.log.Error("User-defined global Caddy config is invalid, skipping it.",
"service", caddyCtr.ServiceName(), "container", caddyCtr.ID, "err", err)
configErrors = append(configErrors,
fmt.Sprintf("service '%s': validation failed: %v", caddyCtr.ServiceName(), err))
} else {
caddyfile = caddyfileCandidate
}
}
}
// There could be multiple service containers for the same service with different custom Caddy configs, for example,
// if the service has been partially updated. The most recent container for each service defines the current custom
// Caddy config for that service.
latestServiceContainers := make(map[string]api.ServiceContainer, len(containers))
for _, ctr := range containers {
if latest, ok := latestServiceContainers[ctr.ServiceName()]; ok {
if ctr.CreatedTime().Compare(latest.CreatedTime()) > 0 {
latestServiceContainers[ctr.ServiceName()] = ctr
}
} else {
latestServiceContainers[ctr.ServiceName()] = ctr
}
}
sortedServiceNames := slices.Sorted(maps.Keys(latestServiceContainers))
// Append a custom Caddy config for each service to the Caddyfile and validate it. If the config for a service
// is invalid, skip it but continue processing other services to ensure the Caddyfile remains valid.
for _, serviceName := range sortedServiceNames {
// Skip the caddy container as we already processed it.
if serviceName == CaddyServiceName {
continue
}
ctr := latestServiceContainers[serviceName]
if ctr.ServiceSpec.CaddyConfig() == "" {
continue
}
// Render the template actions in the service's Caddy config.
tmplCtx := templateContext{
Name: serviceName,
Upstreams: upstreams,
}
renderedConfig, err := renderCaddyfile(tmplCtx, ctr.ServiceSpec.CaddyConfig())
if err != nil {
g.log.Error("Failed to render template directives in user-defined Caddy config for service, skipping it.",
"service", serviceName, "err", err)
configErrors = append(configErrors,
fmt.Sprintf("service '%s': failed to render template: %v", serviceName, err))
continue
}
caddyfileCandidate := fmt.Sprintf("%s\n# User-defined config for service '%s'.\n%s\n",
caddyfile, serviceName, renderedConfig)
if err = g.validator.Validate(ctx, caddyfileCandidate); err != nil {
g.log.Error("User-defined Caddy config for service is invalid, skipping it.",
"service", serviceName, "err", err)
configErrors = append(configErrors, fmt.Sprintf("service '%s': validation failed: %v", serviceName, err))
} else {
caddyfile = caddyfileCandidate
}
}
// Append error summary as comment if there were any invalid configs.
if len(configErrors) > 0 {
errorsComment := "# Skipped invalid user-defined configs:\n"
for _, e := range configErrors {
errorsComment += fmt.Sprintf("# - %s\n", e)
}
caddyfile += "\n" + errorsComment
}
return caddyfileHeader + "\n" + caddyfile, nil
}
func (g *CaddyfileGenerator) generateBaseFromPorts(containers []api.ServiceContainer) (string, error) {
httpHostUpstreams, httpsHostUpstreams := httpUpstreamsFromPorts(containers)
funcs := template.FuncMap{"join": strings.Join}
tmpl, err := template.New("Caddyfile").Funcs(funcs).Parse(caddyfileTemplate)
if err != nil {
return "", fmt.Errorf("parse Caddyfile template: %w", err)
}
data := struct {
VerifyPath string
VerifyResponse string
HTTPHostUpstreams map[string][]string
HTTPSHostUpstreams map[string][]string
}{
VerifyPath: VerifyPath,
VerifyResponse: g.machineID,
HTTPHostUpstreams: httpHostUpstreams,
HTTPSHostUpstreams: httpsHostUpstreams,
}
var buf bytes.Buffer
if err = tmpl.Execute(&buf, data); err != nil {
return "", fmt.Errorf("execute Caddyfile template: %w", err)
}
return buf.String(), nil
}
// httpUpstreamsFromPorts extracts upstreams for HTTP and HTTPS protocols from the published ports of the provided
// service containers. It's expected that all containers are healthy.
func httpUpstreamsFromPorts(containers []api.ServiceContainer) (map[string][]string, map[string][]string) {
// Maps hostnames to lists of upstreams (container IP:port pairs).
httpHostUpstreams := make(map[string][]string)
httpsHostUpstreams := make(map[string][]string)
for _, ctr := range containers {
ip := ctr.UncloudNetworkIP()
if !ip.IsValid() {
// Container is not connected to the uncloud Docker network (could be host network).
continue
}
log := slog.With("container", ctr.ID)
ports, err := ctr.ServicePorts()
if err != nil {
log.Error("Failed to parse service ports for container.", "err", err)
continue
}
for _, port := range ports {
if port.Mode != api.PortModeIngress {
continue
}
switch port.Protocol {
case api.ProtocolHTTP:
upstream := net.JoinHostPort(ip.String(), strconv.Itoa(int(port.ContainerPort)))
httpHostUpstreams[port.Hostname] = append(httpHostUpstreams[port.Hostname], upstream)
case api.ProtocolHTTPS:
upstream := net.JoinHostPort(ip.String(), strconv.Itoa(int(port.ContainerPort)))
httpsHostUpstreams[port.Hostname] = append(httpsHostUpstreams[port.Hostname], upstream)
default:
// TODO: implement L4 ingress routing for TCP and UDP.
log.Error("Unsupported protocol for ingress port.", "port", port)
continue
}
}
}
return httpHostUpstreams, httpsHostUpstreams
}
// serviceUpstreams creates a map of service names to their container IPs.
// Only includes containers connected to the uncloud Docker network.
func serviceUpstreams(containers []api.ServiceContainer) map[string][]string {
upstreams := make(map[string][]string)
for _, ctr := range containers {
ip := ctr.UncloudNetworkIP()
if !ip.IsValid() {
// Container is not connected to the uncloud Docker network (could be host network).
continue
}
serviceName := ctr.ServiceName()
upstreams[serviceName] = append(upstreams[serviceName], ip.String())
}
return upstreams
}
// renderCaddyfile renders a Caddyfile template with the upstreams function and data.
func renderCaddyfile(tmplCtx templateContext, caddyfile string) (string, error) {
funcs := template.FuncMap{
"upstreams": upstreamsTemplateFn(tmplCtx),
}
tmpl, err := template.New("Caddyfile").Funcs(funcs).Parse(caddyfile)
if err != nil {
return "", fmt.Errorf("parse config as Go template: %w", err)
}
var buf bytes.Buffer
if err = tmpl.Execute(&buf, tmplCtx); err != nil {
return "", fmt.Errorf("execute template: %w", err)
}
return buf.String(), nil
}
File diff suppressed because it is too large Load Diff
-142
View File
@@ -1,142 +0,0 @@
package caddyconfig
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/caddyserver/caddy/v2"
)
// CaddyAdminClient is a client for interacting with the Caddy admin API over a Unix socket.
type CaddyAdminClient struct {
socketPath string
client *http.Client
}
func NewCaddyAdminClient(socketPath string) *CaddyAdminClient {
return &CaddyAdminClient{
socketPath: socketPath,
client: &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", socketPath)
},
},
},
}
}
// IsAvailable checks if the local Caddy instance is running and responding to admin API requests.
func (c *CaddyAdminClient) IsAvailable(ctx context.Context) bool {
// Caddy doesn't serve a /ping endpoint. It's a random endpoint we can use to check if Caddy is running.
req, err := http.NewRequestWithContext(ctx, "GET", "http://localhost/ping", nil)
if err != nil {
return false
}
resp, err := c.client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
// Any HTTP response means Caddy is running and accessible.
return true
}
// Adapt converts a Caddyfile to JSON configuration without loading or running it.
func (c *CaddyAdminClient) Adapt(ctx context.Context, caddyfile string) (string, error) {
req, err := http.NewRequestWithContext(ctx, "POST", "http://localhost/adapt", strings.NewReader(caddyfile))
if err != nil {
return "", fmt.Errorf("create adapt request: %w", err)
}
req.Header.Set("Content-Type", "text/caddyfile")
resp, err := c.client.Do(req)
if err != nil {
return "", fmt.Errorf("send adapt request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("read response body: %w", err)
}
if resp.StatusCode == http.StatusOK {
// Parse the response body to extract the result field.
var msg struct {
Result json.RawMessage `json:"result"`
}
if err = json.Unmarshal(body, &msg); err != nil {
return "", fmt.Errorf("parse adapt response: %w", err)
}
return string(msg.Result), nil
}
// If the response is a 400 Bad Request, try to parse the error message from it.
if resp.StatusCode == http.StatusBadRequest {
var apiError caddy.APIError
if err = json.Unmarshal(body, &apiError); err == nil {
return "", errors.New(apiError.Message)
}
}
return "", errors.New(string(body))
}
// Load loads a Caddyfile configuration into the Caddy instance running on the machine.
// Due to a Caddy bug (https://github.com/caddyserver/caddy/issues/7246), we first adapt the Caddyfile to JSON
// and then load the JSON config to get proper error handling.
func (c *CaddyAdminClient) Load(ctx context.Context, caddyfile string) error {
jsonConfig, err := c.Adapt(ctx, caddyfile)
if err != nil {
return fmt.Errorf("adapt Caddyfile to JSON config: %w", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", "http://localhost/load", strings.NewReader(jsonConfig))
if err != nil {
return fmt.Errorf("create load request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("send load request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return nil
}
body, _ := io.ReadAll(resp.Body)
// If the response is a 400 Bad Request, try to parse the error message from it.
if resp.StatusCode == http.StatusBadRequest {
var apiError caddy.APIError
if err = json.Unmarshal(body, &apiError); err == nil {
return fmt.Errorf("caddy responded with error: %s", apiError.Message)
}
}
return fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, string(body))
}
// Validate checks if the provided Caddyfile can be adapted to Caddy JSON config using the running Caddy instance via
// its admin API. It doesn't guarantee that the Caddyfile is actually valid and can be loaded. For example, a tls
// directive with a missing certificate will pass the adaptation but will fail when Caddy tries to load it.
// But this is the best we can do over the admin API.
// TODO: run 'docker exec caddy-container caddy validate' to do proper validation or implement a Caddy module that
// exposes a validation endpoint.
func (c *CaddyAdminClient) Validate(ctx context.Context, caddyfile string) error {
_, err := c.Adapt(ctx, caddyfile)
return err
}
-186
View File
@@ -1,186 +0,0 @@
package caddyconfig
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"path/filepath"
"github.com/psviderski/uncloud/internal/fs"
"github.com/psviderski/uncloud/internal/machine/store"
"github.com/psviderski/uncloud/pkg/api"
)
const (
CaddyServiceName = "caddy"
CaddyGroup = "uncloud"
VerifyPath = "/.uncloud-verify"
)
// Controller monitors container changes in the cluster store and generates a configuration file for Caddy reverse
// proxy. The generated configuration allows Caddy to route external traffic to service containers across the internal
// network.
type Controller struct {
machineID string
caddyfilePath string
generator *CaddyfileGenerator
client *CaddyAdminClient
store *store.Store
log *slog.Logger
}
func NewController(machineID, configDir, adminSock string, store *store.Store) (*Controller, error) {
if err := os.MkdirAll(configDir, 0o750); err != nil {
return nil, fmt.Errorf("create directory for Caddy configuration '%s': %w", configDir, err)
}
if err := fs.Chown(configDir, "", CaddyGroup); err != nil {
return nil, fmt.Errorf("change owner of directory for Caddy configuration '%s': %w", configDir, err)
}
log := slog.With("component", "caddy-controller")
client := NewCaddyAdminClient(adminSock)
generator := NewCaddyfileGenerator(machineID, client, log)
return &Controller{
machineID: machineID,
caddyfilePath: filepath.Join(configDir, "Caddyfile"),
generator: generator,
client: client,
store: store,
log: log,
}, nil
}
func (c *Controller) Run(ctx context.Context) error {
containers, changes, err := c.store.SubscribeContainers(ctx)
if err != nil {
return fmt.Errorf("subscribe to container changes: %w", err)
}
c.log.Info("Subscribed to container changes in the cluster to generate Caddy configuration.")
containers = filterHealthyContainers(containers)
c.generateAndLoadCaddyfile(ctx, containers)
// TODO: left for backward compatibility, remove later.
if err = c.generateJSONConfig(containers); err != nil {
c.log.Error("Failed to generate Caddy JSON configuration to disk.", "err", err)
}
for {
select {
case _, ok := <-changes:
if !ok {
return fmt.Errorf("containers subscription failed")
}
c.log.Info("Cluster containers changed, updating Caddy configuration.")
containers, err = c.store.ListContainers(ctx, store.ListOptions{})
if err != nil {
c.log.Error("Failed to list containers.", "err", err)
continue
}
containers = filterHealthyContainers(containers)
c.generateAndLoadCaddyfile(ctx, containers)
// TODO: left for backward compatibility, remove later.
if err = c.generateJSONConfig(containers); err != nil {
c.log.Error("Failed to generate Caddy JSON configuration to disk.", "err", err)
}
case <-ctx.Done():
return nil
}
}
}
// filterHealthyContainers filters out containers that are not healthy.
// TODO: Filters out containers from this machine that are likely unavailable. The availability can be determined
// by the cluster membership state of the machine that the container is running on. Implement machine membership
// check using Corrossion Admin client.
func filterHealthyContainers(containers []store.ContainerRecord) []store.ContainerRecord {
healthy := make([]store.ContainerRecord, 0, len(containers))
for _, cr := range containers {
if cr.Container.Healthy() {
healthy = append(healthy, cr)
}
}
return healthy
}
func (c *Controller) generateAndLoadCaddyfile(ctx context.Context, containers []store.ContainerRecord) {
// Check if Caddy is available before attempting to generate and load config.
caddyAvailable := c.client.IsAvailable(ctx)
caddyfile, err := c.generator.Generate(ctx, containers, caddyAvailable)
if err != nil {
c.log.Error("Failed to generate Caddyfile configuration.", "err", err)
return
}
if !caddyAvailable {
// Caddy is not running so the generated Caddyfile should not include user-defined configs thus must be valid.
// It's safe to write the config to disk so that when Caddy is deployed on this machine, it can pick it up.
if err = c.writeCaddyfile(caddyfile); err != nil {
c.log.Error("Failed to write Caddyfile to disk.", "err", err)
return
}
c.log.Debug("Caddy is not running on this machine, skipping configuration load.", "path", c.caddyfilePath)
return
}
// Caddy is available, try to load the config which may fail if the config is invalid. Generally, a config can
// pass the adaptation/validation step but still fail to load, for example, if it references resources that are
// not available.
if err = c.client.Load(ctx, caddyfile); err != nil {
c.log.Error("Failed to load new Caddy configuration into local Caddy instance.",
"err", err, "path", c.caddyfilePath)
// Don't write invalid config to disk.
return
}
// Config loaded successfully, now write it to disk.
if err = c.writeCaddyfile(caddyfile); err != nil {
c.log.Error("Failed to write Caddyfile to disk after successful load.", "err", err)
// Config is already loaded in Caddy, so this is not critical.
}
c.log.Info("New Caddy configuration loaded into local Caddy instance.", "path", c.caddyfilePath)
}
// writeCaddyfile writes the Caddyfile content to disk with proper permissions.
func (c *Controller) writeCaddyfile(caddyfile string) error {
if err := os.WriteFile(c.caddyfilePath, []byte(caddyfile), 0o640); err != nil {
return fmt.Errorf("write Caddyfile to file '%s': %w", c.caddyfilePath, err)
}
if err := fs.Chown(c.caddyfilePath, "", CaddyGroup); err != nil {
return fmt.Errorf("change owner of Caddyfile '%s': %w", c.caddyfilePath, err)
}
return nil
}
func (c *Controller) generateJSONConfig(containers []store.ContainerRecord) error {
serviceContainers := make([]api.ServiceContainer, len(containers))
for i, cr := range containers {
serviceContainers[i] = cr.Container
}
config, err := GenerateJSONConfig(serviceContainers, c.machineID)
if err != nil {
return err
}
configBytes, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("marshal Caddy configuration: %w", err)
}
configPath := filepath.Join(filepath.Dir(c.caddyfilePath), "caddy.json")
if err = os.WriteFile(configPath, configBytes, 0o640); err != nil {
return fmt.Errorf("write Caddy configuration to file '%s': %w", configPath, err)
}
if err = fs.Chown(configPath, "", CaddyGroup); err != nil {
return fmt.Errorf("change owner of Caddy configuration file '%s': %w", configPath, err)
}
return nil
}
@@ -1,95 +0,0 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package caddyconfig
import (
"context"
mock "github.com/stretchr/testify/mock"
)
// NewMockCaddyfileValidator creates a new instance of MockCaddyfileValidator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockCaddyfileValidator(t interface {
mock.TestingT
Cleanup(func())
}) *MockCaddyfileValidator {
mock := &MockCaddyfileValidator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// MockCaddyfileValidator is an autogenerated mock type for the CaddyfileValidator type
type MockCaddyfileValidator struct {
mock.Mock
}
type MockCaddyfileValidator_Expecter struct {
mock *mock.Mock
}
func (_m *MockCaddyfileValidator) EXPECT() *MockCaddyfileValidator_Expecter {
return &MockCaddyfileValidator_Expecter{mock: &_m.Mock}
}
// Validate provides a mock function for the type MockCaddyfileValidator
func (_mock *MockCaddyfileValidator) Validate(ctx context.Context, caddyfile string) error {
ret := _mock.Called(ctx, caddyfile)
if len(ret) == 0 {
panic("no return value specified for Validate")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = returnFunc(ctx, caddyfile)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockCaddyfileValidator_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
type MockCaddyfileValidator_Validate_Call struct {
*mock.Call
}
// Validate is a helper method to define mock.On call
// - ctx context.Context
// - caddyfile string
func (_e *MockCaddyfileValidator_Expecter) Validate(ctx interface{}, caddyfile interface{}) *MockCaddyfileValidator_Validate_Call {
return &MockCaddyfileValidator_Validate_Call{Call: _e.mock.On("Validate", ctx, caddyfile)}
}
func (_c *MockCaddyfileValidator_Validate_Call) Run(run func(ctx context.Context, caddyfile string)) *MockCaddyfileValidator_Validate_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 context.Context
if args[0] != nil {
arg0 = args[0].(context.Context)
}
var arg1 string
if args[1] != nil {
arg1 = args[1].(string)
}
run(
arg0,
arg1,
)
})
return _c
}
func (_c *MockCaddyfileValidator_Validate_Call) Return(err error) *MockCaddyfileValidator_Validate_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockCaddyfileValidator_Validate_Call) RunAndReturn(run func(ctx context.Context, caddyfile string) error) *MockCaddyfileValidator_Validate_Call {
_c.Call.Return(run)
return _c
}
-39
View File
@@ -1,39 +0,0 @@
package caddyconfig
import (
"context"
"os"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/psviderski/uncloud/internal/machine/api/pb"
)
// Server implements the gRPC Caddy service.
type Server struct {
pb.UnimplementedCaddyServer
service *Service
}
func NewServer(service *Service) *Server {
return &Server{service: service}
}
// GetConfig retrieves the current Caddy configuration from the machine.
func (s *Server) GetConfig(ctx context.Context, _ *emptypb.Empty) (*pb.GetCaddyConfigResponse, error) {
caddyfile, modifiedAt, err := s.service.Caddyfile()
if err != nil {
if os.IsNotExist(err) {
return nil, status.Errorf(codes.NotFound, err.Error())
}
return nil, status.Errorf(codes.Internal, err.Error())
}
return &pb.GetCaddyConfigResponse{
Caddyfile: caddyfile,
ModifiedAt: timestamppb.New(modifiedAt),
}, nil
}
-35
View File
@@ -1,35 +0,0 @@
package caddyconfig
import (
"fmt"
"os"
"path/filepath"
"time"
)
// Service provides methods to interact with the Caddy configuration on the machine.
type Service struct {
configDir string
}
// NewService creates a new Service instance with the specified Caddy configuration directory.
func NewService(configDir string) *Service {
return &Service{configDir: configDir}
}
// Caddyfile retrieves the current Caddy configuration (Caddyfile) from the machine's config directory.
func (s *Service) Caddyfile() (string, time.Time, error) {
path := filepath.Join(s.configDir, "Caddyfile")
content, err := os.ReadFile(path)
if err != nil {
return "", time.Time{}, fmt.Errorf("read Caddyfile from file '%s': %w", path, err)
}
// Get the file modification time.
fileInfo, err := os.Stat(path)
if err != nil {
return "", time.Time{}, fmt.Errorf("get Caddyfile file info '%s': %w", path, err)
}
return string(content), fileInfo.ModTime(), nil
}
-77
View File
@@ -1,77 +0,0 @@
package caddyconfig
import (
"fmt"
"net"
"strconv"
"strings"
)
// templateContext holds the data available to Caddyfile templates.
type templateContext struct {
// Name is the current service name.
Name string
// Upstreams maps service names to their container IPs.
Upstreams map[string][]string
}
// upstreamsTemplateFn returns a template function that generates a space separated string of upstreams for the service.
// It optionally accepts a service name and a port number: {{upstreams [service-name] [port]}}.
func upstreamsTemplateFn(tmplCtx templateContext) func(args ...any) (string, error) {
return func(args ...any) (string, error) {
var serviceName string
var port int
// Parse arguments.
switch len(args) {
case 0:
// Current service, default port.
serviceName = tmplCtx.Name
case 1:
// Either port (int) for current service or service name (string).
switch arg := args[0].(type) {
case int:
serviceName = tmplCtx.Name
port = arg
case string:
serviceName = arg
port = 0
default:
return "", fmt.Errorf("upstreams function: invalid argument type: %T", arg)
}
case 2:
// Service name and port.
name, ok := args[0].(string)
if !ok {
return "", fmt.Errorf("upstreams function: first argument must be service name (string)")
}
serviceName = name
p, ok := args[1].(int)
if !ok {
return "", fmt.Errorf("upstreams function: second argument must be port (int)")
}
port = p
default:
return "", fmt.Errorf("upstreams function: too many arguments; expected 0-2, got %d", len(args))
}
ips, ok := tmplCtx.Upstreams[serviceName]
if !ok || len(ips) == 0 {
// No upstreams available.
return "", nil
}
// Build the space separated upstreams string.
var upstreams []string
for _, ip := range ips {
if port > 0 {
upstreams = append(upstreams, net.JoinHostPort(ip, strconv.Itoa(port)))
} else {
upstreams = append(upstreams, ip)
}
}
return strings.Join(upstreams, " "), nil
}
}
@@ -1,14 +1,15 @@
package caddyconfig package caddyfile
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog"
"maps" "maps"
"net"
"net/http" "net/http"
"slices" "slices"
"strconv" "strconv"
"time"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
@@ -17,15 +18,52 @@ import (
"github.com/psviderski/uncloud/pkg/api" "github.com/psviderski/uncloud/pkg/api"
) )
func GenerateJSONConfig(containers []api.ServiceContainer, verifyResponse string) (*caddy.Config, error) { func GenerateConfig(containers []api.ServiceContainer, verifyResponse string) (*caddy.Config, error) {
httpHostUpstreams, httpsHostUpstreams := httpUpstreamsFromPorts(containers) // Maps hostnames to lists of upstreams (container IP:port pairs).
httpHostUpstreams := make(map[string][]string)
httpsHostUpstreams := make(map[string][]string)
for _, ctr := range containers {
if !ctr.Healthy() {
continue
}
ip := ctr.UncloudNetworkIP()
if !ip.IsValid() {
// Container is not connected to the uncloud Docker network (could be host network).
continue
}
log := slog.With("container", ctr.ID)
ports, err := ctr.ServicePorts()
if err != nil {
log.Error("Failed to parse service ports for container.", "err", err)
continue
}
for _, port := range ports {
if port.Mode != api.PortModeIngress {
continue
}
switch port.Protocol {
case api.ProtocolHTTP:
upstream := net.JoinHostPort(ip.String(), strconv.Itoa(int(port.ContainerPort)))
httpHostUpstreams[port.Hostname] = append(httpHostUpstreams[port.Hostname], upstream)
case api.ProtocolHTTPS:
upstream := net.JoinHostPort(ip.String(), strconv.Itoa(int(port.ContainerPort)))
httpsHostUpstreams[port.Hostname] = append(httpsHostUpstreams[port.Hostname], upstream)
default:
// TODO: implement L4 ingress routing for TCP and UDP.
log.Error("Unsupported protocol for ingress port.", "port", port)
continue
}
}
}
var warnings []caddyconfig.Warning var warnings []caddyconfig.Warning
servers := make(map[string]*caddyhttp.Server) servers := make(map[string]*caddyhttp.Server)
servers["http"] = &caddyhttp.Server{ servers["http"] = &caddyhttp.Server{
Listen: []string{fmt.Sprintf(":%d", caddyhttp.DefaultHTTPPort)}, Listen: []string{fmt.Sprintf(":%d", caddyhttp.DefaultHTTPPort)},
// All http requests to this server are logged to the default logger.
Logs: &caddyhttp.ServerLogConfig{},
Routes: append( Routes: append(
hostUpstreamsToRoutes(httpHostUpstreams, &warnings), hostUpstreamsToRoutes(httpHostUpstreams, &warnings),
// Add a route to respond with a static verification response at the /.uncloud-verify path. // Add a route to respond with a static verification response at the /.uncloud-verify path.
@@ -34,8 +72,6 @@ func GenerateJSONConfig(containers []api.ServiceContainer, verifyResponse string
} }
servers["https"] = &caddyhttp.Server{ servers["https"] = &caddyhttp.Server{
Listen: []string{fmt.Sprintf(":%d", caddyhttp.DefaultHTTPSPort)}, Listen: []string{fmt.Sprintf(":%d", caddyhttp.DefaultHTTPSPort)},
// All https requests to this server are logged to the default logger.
Logs: &caddyhttp.ServerLogConfig{},
Routes: hostUpstreamsToRoutes(httpsHostUpstreams, &warnings), Routes: hostUpstreamsToRoutes(httpsHostUpstreams, &warnings),
} }
@@ -76,16 +112,6 @@ func hostUpstreamsToRoutes(hostUpstreams map[string][]string, warnings *[]caddyc
} }
} }
handler := &reverseproxy.Handler{ handler := &reverseproxy.Handler{
HealthChecks: &reverseproxy.HealthChecks{
// Enable passive health checks to automatically detect unhealthy upstreams.
Passive: &reverseproxy.PassiveHealthChecks{
FailDuration: caddy.Duration(30 * time.Second),
},
},
LoadBalancing: &reverseproxy.LoadBalancing{
// Retry failed requests to skip over temporarily unavailable upstreams.
Retries: 3,
},
Upstreams: upstreamPool, Upstreams: upstreamPool,
} }
@@ -1,4 +1,4 @@
package caddyconfig package caddyfile
import ( import (
"strings" "strings"
@@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestGenerateJSONConfig(t *testing.T) { func TestGenerateConfig(t *testing.T) {
configWithoutServices := `{ configWithoutServices := `{
"servers": { "servers": {
"http": { "http": {
@@ -25,12 +25,10 @@ func TestGenerateJSONConfig(t *testing.T) {
"handler": "static_response", "handler": "static_response",
"status_code": 200 "status_code": 200
}] }]
}], }]
"logs": {}
}, },
"https": { "https": {
"listen": [":443"], "listen": [":443"]
"logs": {}
} }
} }
}` }`
@@ -62,14 +60,6 @@ func TestGenerateJSONConfig(t *testing.T) {
"match": [{"host": ["app.example.com"]}], "match": [{"host": ["app.example.com"]}],
"handle": [{ "handle": [{
"handler": "reverse_proxy", "handler": "reverse_proxy",
"health_checks": {
"passive": {
"fail_duration": 30000000000
}
},
"load_balancing": {
"retries": 3
},
"upstreams": [{"dial": "10.210.0.2:8080"}] "upstreams": [{"dial": "10.210.0.2:8080"}]
}] }]
}, },
@@ -81,12 +71,10 @@ func TestGenerateJSONConfig(t *testing.T) {
"status_code": 200 "status_code": 200
}] }]
} }
], ]
"logs": {}
}, },
"https": { "https": {
"listen": [":443"], "listen": [":443"]
"logs": {}
} }
} }
}`, }`,
@@ -107,14 +95,6 @@ func TestGenerateJSONConfig(t *testing.T) {
"match": [{"host": ["app.example.com"]}], "match": [{"host": ["app.example.com"]}],
"handle": [{ "handle": [{
"handler": "reverse_proxy", "handler": "reverse_proxy",
"health_checks": {
"passive": {
"fail_duration": 30000000000
}
},
"load_balancing": {
"retries": 3
},
"upstreams": [ "upstreams": [
{"dial": "10.210.0.2:8080"}, {"dial": "10.210.0.2:8080"},
{"dial": "10.210.0.3:8080"} {"dial": "10.210.0.3:8080"}
@@ -129,12 +109,10 @@ func TestGenerateJSONConfig(t *testing.T) {
"status_code": 200 "status_code": 200
}] }]
} }
], ]
"logs": {}
}, },
"https": { "https": {
"listen": [":443"], "listen": [":443"]
"logs": {}
} }
} }
}`, }`,
@@ -158,8 +136,7 @@ func TestGenerateJSONConfig(t *testing.T) {
"status_code": 200 "status_code": 200
}] }]
} }
], ]
"logs": {}
}, },
"https": { "https": {
"listen": [":443"], "listen": [":443"],
@@ -168,19 +145,10 @@ func TestGenerateJSONConfig(t *testing.T) {
"match": [{"host": ["secure.example.com"]}], "match": [{"host": ["secure.example.com"]}],
"handle": [{ "handle": [{
"handler": "reverse_proxy", "handler": "reverse_proxy",
"health_checks": {
"passive": {
"fail_duration": 30000000000
}
},
"load_balancing": {
"retries": 3
},
"upstreams": [{"dial": "10.210.0.2:8000"}] "upstreams": [{"dial": "10.210.0.2:8000"}]
}] }]
} }
], ]
"logs": {}
} }
} }
}`, }`,
@@ -212,14 +180,6 @@ func TestGenerateJSONConfig(t *testing.T) {
"match": [{"host": ["app.example.com"]}], "match": [{"host": ["app.example.com"]}],
"handle": [{ "handle": [{
"handler": "reverse_proxy", "handler": "reverse_proxy",
"health_checks": {
"passive": {
"fail_duration": 30000000000
}
},
"load_balancing": {
"retries": 3
},
"upstreams": [ "upstreams": [
{"dial": "10.210.0.2:8080"}, {"dial": "10.210.0.2:8080"},
{"dial": "10.210.0.3:8080"}, {"dial": "10.210.0.3:8080"},
@@ -231,14 +191,6 @@ func TestGenerateJSONConfig(t *testing.T) {
"match": [{"host": ["web.example.com"]}], "match": [{"host": ["web.example.com"]}],
"handle": [{ "handle": [{
"handler": "reverse_proxy", "handler": "reverse_proxy",
"health_checks": {
"passive": {
"fail_duration": 30000000000
}
},
"load_balancing": {
"retries": 3
},
"upstreams": [ "upstreams": [
{"dial": "10.210.0.2:8000"}, {"dial": "10.210.0.2:8000"},
{"dial": "10.210.0.4:8000"}, {"dial": "10.210.0.4:8000"},
@@ -254,8 +206,7 @@ func TestGenerateJSONConfig(t *testing.T) {
"status_code": 200 "status_code": 200
}] }]
} }
], ]
"logs": {}
}, },
"https": { "https": {
"listen": [":443"], "listen": [":443"],
@@ -264,14 +215,6 @@ func TestGenerateJSONConfig(t *testing.T) {
"match": [{"host": ["secure.example.com"]}], "match": [{"host": ["secure.example.com"]}],
"handle": [{ "handle": [{
"handler": "reverse_proxy", "handler": "reverse_proxy",
"health_checks": {
"passive": {
"fail_duration": 30000000000
}
},
"load_balancing": {
"retries": 3
},
"upstreams": [ "upstreams": [
{"dial": "10.210.0.3:8888"}, {"dial": "10.210.0.3:8888"},
{"dial": "10.210.0.4:8888"}, {"dial": "10.210.0.4:8888"},
@@ -279,8 +222,7 @@ func TestGenerateJSONConfig(t *testing.T) {
] ]
}] }]
} }
], ]
"logs": {}
} }
} }
}`, }`,
@@ -312,11 +254,63 @@ func TestGenerateJSONConfig(t *testing.T) {
want: configWithoutServices, want: configWithoutServices,
wantErr: false, wantErr: false,
}, },
{
name: "restarting container ignored",
containers: []api.ServiceContainer{
newRestartingContainer("10.210.0.2", "app.example.com:8080/http"),
},
want: configWithoutServices,
wantErr: false,
},
{
name: "stopped container ignored",
containers: []api.ServiceContainer{
newStoppedContainer("10.210.0.2", "app.example.com:8080/http"),
},
want: configWithoutServices,
wantErr: false,
},
{
name: "mix of running, restarting, and stopped containers",
containers: []api.ServiceContainer{
newContainer("10.210.0.2", "app.example.com:8080/http"),
newRestartingContainer("10.210.0.3", "app.example.com:8080/http"),
newStoppedContainer("10.210.0.4", "app.example.com:8080/http"),
},
want: `{
"servers": {
"http": {
"listen": [":80"],
"routes": [
{
"match": [{"host": ["app.example.com"]}],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "10.210.0.2:8080"}]
}]
},
{
"match": [{"path": ["/.uncloud-verify"]}],
"handle": [{
"body": "verification-response-body",
"handler": "static_response",
"status_code": 200
}]
}
]
},
"https": {
"listen": [":443"]
}
}
}`,
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
config, err := GenerateJSONConfig(tt.containers, "verification-response-body") config, err := GenerateConfig(tt.containers, "verification-response-body")
if tt.wantErr { if tt.wantErr {
assert.Error(t, err) assert.Error(t, err)
@@ -377,3 +371,15 @@ func newContainerWithoutNetwork(ports ...string) api.ServiceContainer {
}, },
}}} }}}
} }
func newRestartingContainer(ip string, ports ...string) api.ServiceContainer {
ctr := newContainer(ip, ports...)
ctr.Container.State.Restarting = true
return ctr
}
func newStoppedContainer(ip string, ports ...string) api.ServiceContainer {
ctr := newContainer(ip, ports...)
ctr.Container.State.Running = false
return ctr
}
+125
View File
@@ -0,0 +1,125 @@
package caddyfile
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"path/filepath"
"github.com/psviderski/uncloud/internal/fs"
"github.com/psviderski/uncloud/internal/machine/store"
"github.com/psviderski/uncloud/pkg/api"
)
const (
CaddyGroup = "uncloud"
VerifyPath = "/.uncloud-verify"
)
// Controller monitors container changes in the cluster store and generates a configuration file for Caddy reverse
// proxy. The generated Caddyfile allows Caddy to route external traffic to service containers across the internal
// network.
type Controller struct {
store *store.Store
path string
verifyResponse string
}
func NewController(store *store.Store, path string, verifyResponse string) (*Controller, error) {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0750); err != nil {
return nil, fmt.Errorf("create parent directory for Caddy configuration '%s': %w", dir, err)
}
if err := fs.Chown(dir, "", CaddyGroup); err != nil {
return nil, fmt.Errorf("change owner of parent directory for Caddy configuration '%s': %w", dir, err)
}
return &Controller{
store: store,
path: path,
verifyResponse: verifyResponse,
}, nil
}
func (c *Controller) Run(ctx context.Context) error {
containerRecords, changes, err := c.store.SubscribeContainers(ctx)
if err != nil {
return fmt.Errorf("subscribe to container changes: %w", err)
}
slog.Info("Subscribed to container changes in the cluster to generate Caddy configuration.")
containers, err := c.filterAvailableContainers(containerRecords)
if err != nil {
return fmt.Errorf("filter available containers: %w", err)
}
if err = c.generateConfig(containers); err != nil {
return fmt.Errorf("generate Caddy configuration: %w", err)
}
for {
select {
case _, ok := <-changes:
if !ok {
return fmt.Errorf("containers subscription failed")
}
slog.Debug("Cluster containers changed, updating Caddy configuration.")
containerRecords, err = c.store.ListContainers(ctx, store.ListOptions{})
if err != nil {
slog.Error("Failed to list containers.", "err", err)
continue
}
containers, err = c.filterAvailableContainers(containerRecords)
if err != nil {
slog.Error("Failed to filter available containers.", "err", err)
continue
}
if err = c.generateConfig(containers); err != nil {
slog.Error("Failed to generate Caddy configuration.", "err", err)
}
slog.Debug("Updated Caddy configuration.", "path", c.path)
case <-ctx.Done():
return nil
}
}
}
// filterAvailableContainers filters out containers from this machine that are likely unavailable. The availability
// is determined by the cluster membership state of the machine that the container is running on.
// TODO: implement machine membership check using Corrossion Admin client.
func (c *Controller) filterAvailableContainers(
containerRecords []store.ContainerRecord,
) ([]api.ServiceContainer, error) {
containers := make([]api.ServiceContainer, len(containerRecords))
for i, cr := range containerRecords {
containers[i] = api.ServiceContainer{
Container: cr.Container,
// TODO: restore ServiceSpec from the container record once it's saved in the store.
}
}
return containers, nil
}
func (c *Controller) generateConfig(containers []api.ServiceContainer) error {
config, err := GenerateConfig(containers, c.verifyResponse)
if err != nil {
return err
}
configBytes, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("marshal Caddy configuration: %w", err)
}
if err = os.WriteFile(c.path, configBytes, 0640); err != nil {
return fmt.Errorf("write Caddy configuration to file '%s': %w", c.path, err)
}
if err = fs.Chown(c.path, "", CaddyGroup); err != nil {
return fmt.Errorf("change owner of Caddy configuration file '%s': %w", c.path, err)
}
return nil
}
+3 -108
View File
@@ -5,18 +5,17 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"log/slog" "log/slog"
"net/netip" "net/netip"
"time" "time"
"github.com/psviderski/uncloud/internal/corrosion" "github.com/psviderski/uncloud/internal/corrosion"
"github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/machine/network" "github.com/psviderski/uncloud/internal/machine/network"
"github.com/psviderski/uncloud/internal/machine/store" "github.com/psviderski/uncloud/internal/machine/store"
"github.com/psviderski/uncloud/internal/secret" "github.com/psviderski/uncloud/internal/secret"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
) )
type Cluster struct { type Cluster struct {
@@ -198,89 +197,6 @@ func (c *Cluster) AddMachine(ctx context.Context, req *pb.AddMachineRequest) (*p
return resp, nil return resp, nil
} }
// UpdateMachine updates machine configuration in the cluster.
func (c *Cluster) UpdateMachine(ctx context.Context, req *pb.UpdateMachineRequest) (*pb.UpdateMachineResponse, error) {
if err := c.checkInitialised(ctx); err != nil {
return nil, err
}
if req.MachineId == "" {
return nil, status.Error(codes.InvalidArgument, "machine_id not set")
}
// Get the current machine info
currentMachine, err := c.store.GetMachine(ctx, req.MachineId)
if err != nil {
if errors.Is(err, store.ErrMachineNotFound) {
return nil, status.Errorf(codes.NotFound, "machine not found: %s", req.MachineId)
}
return nil, status.Errorf(codes.Internal, "failed to get machine: %v", err)
}
// Create a copy of the current machine for updating
updatedMachine := &pb.MachineInfo{
Id: currentMachine.Id,
Name: currentMachine.Name,
Network: currentMachine.Network,
PublicIp: currentMachine.PublicIp,
}
// Apply updates from the request
if req.Name != nil {
// Check for empty name
if *req.Name == "" {
return nil, status.Error(codes.InvalidArgument, "machine name cannot be empty")
}
// Check for duplicate names (excluding the current machine)
if *req.Name != currentMachine.Name {
machines, err := c.store.ListMachines(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "list machines: %v", err)
}
for _, m := range machines {
if m.Id != req.MachineId && m.Name == *req.Name {
return nil, status.Errorf(codes.AlreadyExists, "machine with name %q already exists", *req.Name)
}
}
}
updatedMachine.Name = *req.Name
}
if req.PublicIp != nil {
// Check if this is an empty IP (used to signal removal)
if len(req.PublicIp.Ip) == 0 {
// User wants to remove public IP
updatedMachine.PublicIp = nil
} else {
// Validate and set the new IP
ip, err := req.PublicIp.ToAddr()
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid public IP: %v", err)
}
if !ip.IsValid() {
return nil, status.Error(codes.InvalidArgument, "invalid public IP")
}
updatedMachine.PublicIp = req.PublicIp
}
}
if req.Endpoints != nil {
updatedMachine.Network.Endpoints = req.Endpoints
}
// Update the machine in the store
if err = c.store.UpdateMachine(ctx, updatedMachine); err != nil {
if errors.Is(err, store.ErrMachineNotFound) {
return nil, status.Errorf(codes.NotFound, "machine not found: %s", req.MachineId)
}
return nil, status.Errorf(codes.Internal, "update machine: %v", err)
}
slog.Info("Machine configuration updated in the cluster.",
"id", updatedMachine.Id, "name", updatedMachine.Name)
resp := &pb.UpdateMachineResponse{Machine: updatedMachine}
return resp, nil
}
// ListMachines lists all machines in the cluster including their membership states. // ListMachines lists all machines in the cluster including their membership states.
func (c *Cluster) ListMachines(ctx context.Context, _ *emptypb.Empty) (*pb.ListMachinesResponse, error) { func (c *Cluster) ListMachines(ctx context.Context, _ *emptypb.Empty) (*pb.ListMachinesResponse, error) {
if err := c.checkInitialised(ctx); err != nil { if err := c.checkInitialised(ctx); err != nil {
@@ -326,24 +242,3 @@ func (c *Cluster) ListMachines(ctx context.Context, _ *emptypb.Empty) (*pb.ListM
return &pb.ListMachinesResponse{Machines: members}, nil return &pb.ListMachinesResponse{Machines: members}, nil
} }
// RemoveMachine removes a machine from the cluster.
func (c *Cluster) RemoveMachine(ctx context.Context, req *pb.RemoveMachineRequest) (*emptypb.Empty, error) {
if err := c.checkInitialised(ctx); err != nil {
return nil, err
}
if req.Id == "" {
return nil, status.Error(codes.InvalidArgument, "machine ID not set")
}
if err := c.store.DeleteMachine(ctx, req.Id); err != nil {
if errors.Is(err, store.ErrMachineNotFound) {
return nil, status.Errorf(codes.NotFound, "machine not found: %s", req.Id)
}
return nil, status.Errorf(codes.Internal, "delete machine from store: %v", err)
}
slog.Info("Machine removed from the cluster.", "id", req.Id)
return &emptypb.Empty{}, nil
}
-1
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/psviderski/uncloud/internal/dns" "github.com/psviderski/uncloud/internal/dns"
"github.com/psviderski/uncloud/internal/machine/api/pb" "github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/machine/store" "github.com/psviderski/uncloud/internal/machine/store"
+1 -2
View File
@@ -3,9 +3,8 @@ package cluster
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip"
"go4.org/netipx" "go4.org/netipx"
"net/netip"
) )
const DefaultSubnetBits = 24 const DefaultSubnetBits = 24
-1
View File
@@ -2,7 +2,6 @@ package cluster
import ( import (
"fmt" "fmt"
"github.com/psviderski/uncloud/internal/secret" "github.com/psviderski/uncloud/internal/secret"
) )
-6
View File
@@ -1,6 +0,0 @@
package constants
const (
// MachineAPIPort is the port for the Machine API service on the management WireGuard network.
MachineAPIPort = 51000
)

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