mirror of
https://github.com/psviderski/uncloud.git
synced 2026-10-06 21:29:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c58108aa82 | ||
|
|
5d7952952a | ||
|
|
87e99ae369 | ||
|
|
f78a8ca8b5 | ||
|
|
e4bd1ad443 | ||
|
|
0e53f505c0 | ||
|
|
9c21ae6b08 | ||
|
|
70de7b1b8f | ||
|
|
af277639dd | ||
|
|
25ca0a57e8 | ||
|
|
4dae187aa5 | ||
|
|
930e7ea637 | ||
|
|
e50c5fbe57 | ||
|
|
e4455e936a | ||
|
|
ebecf334d7 | ||
|
|
417e402c73 | ||
|
|
9ee3ca15eb | ||
|
|
3482eeefab | ||
|
|
ace8cbf974 | ||
|
|
45d33d87dd | ||
|
|
5c33e4be86 | ||
|
|
88dda435e7 | ||
|
|
ae940c3003 | ||
|
|
e525bdeb7c | ||
|
|
bc09954cbd | ||
|
|
f68c9859d9 | ||
|
|
c65f352250 | ||
|
|
b28c458029 | ||
|
|
3fc1cddf01 | ||
|
|
54a7328a34 | ||
|
|
47a4a9158e | ||
|
|
ad2e1a06d5 | ||
|
|
81d36edc66 | ||
|
|
5fb80e2071 | ||
|
|
d1fe23dec0 | ||
|
|
cec3c75a10 | ||
|
|
751ea97f47 | ||
|
|
abb85f8734 | ||
|
|
37f817aa58 | ||
|
|
db55df64a2 | ||
|
|
e9e7b3a61e | ||
|
|
e81e130544 | ||
|
|
1e471d3ec4 | ||
|
|
f7be7c55c9 | ||
|
|
6ab02f95fe | ||
|
|
9a4098694c | ||
|
|
992b819f5b | ||
|
|
842f796e38 | ||
|
|
0f6c03240b | ||
|
|
c6d0371f15 | ||
|
|
5e01db5e20 | ||
|
|
8aedc2bf19 | ||
|
|
7a88865f64 | ||
|
|
a24fc11caf | ||
|
|
c7e0e36daa | ||
|
|
01426ca03d | ||
|
|
3f86b3de44 | ||
|
|
aaf67687d2 | ||
|
|
1c29d4d5d0 | ||
|
|
00d68d9465 | ||
|
|
43bf2baaf7 | ||
|
|
613cd6e418 | ||
|
|
7a7a313426 | ||
|
|
a5ab6e3788 | ||
|
|
5a956e72de | ||
|
|
ac56754281 | ||
|
|
b7e224a1ef | ||
|
|
351698c280 | ||
|
|
fa77edf53e | ||
|
|
b99abb7c39 | ||
|
|
c2ae11a293 | ||
|
|
f1555259de | ||
|
|
50f8fbcfda | ||
|
|
73f29092ff | ||
|
|
08b24af341 | ||
|
|
b9c54f1ff5 | ||
|
|
4d76dd601c | ||
|
|
3af9936d27 | ||
|
|
dc721e511e | ||
|
|
054b3b8fd4 | ||
|
|
b3896ff642 | ||
|
|
5b2823de1e | ||
|
|
0007f76d09 | ||
|
|
f8e6d1caaa | ||
|
|
e684795aee | ||
|
|
7964273552 | ||
|
|
4ad4bce29d | ||
|
|
d670d18810 | ||
|
|
a1957a156e | ||
|
|
6266e9ec3a | ||
|
|
7c4fcde88d | ||
|
|
d36b28c55a | ||
|
|
6b41340f1d | ||
|
|
4b9ea43402 | ||
|
|
e5f23a9189 | ||
|
|
15e3e32b47 |
No files matched your search
@@ -2,6 +2,7 @@
|
||||
*
|
||||
|
||||
# Allow files and directories.
|
||||
!api/
|
||||
!cmd/
|
||||
!internal/
|
||||
!pkg/
|
||||
|
||||
@@ -18,3 +18,6 @@ indent_size = 4
|
||||
[*.sh]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[release-notes-*.md]
|
||||
max_line_length = off
|
||||
@@ -57,7 +57,8 @@ jobs:
|
||||
|
||||
The commands below show how to install the nightly version of uncloud.
|
||||
|
||||
> **Warning**: These are nightly development builds and may be unstable.
|
||||
> [!WARNING]
|
||||
> These are nightly development builds and may be unstable.
|
||||
|
||||
**Install uncloud CLI:**
|
||||
|
||||
|
||||
@@ -44,3 +44,4 @@ node_modules/
|
||||
.devcontainer/
|
||||
|
||||
.zed/
|
||||
research/
|
||||
@@ -188,7 +188,7 @@ uc context use <name> # Switch context
|
||||
- **Package naming**: Use clear, descriptive names
|
||||
- **Error handling**: Wrap errors with context using `fmt.Errorf`
|
||||
- **Logging**: Use structured logging with levels
|
||||
- **gRPC**: Services defined in `internal/machine/api/pb/`
|
||||
- **gRPC**: Services defined in `api/pb/`
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -196,7 +196,6 @@ uc context use <name> # Switch context
|
||||
- Unit tests alongside source files (`*_test.go`)
|
||||
- Integration tests in `test/e2e/`
|
||||
- Test fixtures in `test/fixtures/`
|
||||
- Use table driven tests whenever possible
|
||||
- Use the `testify` library for assertions (e.g., `require.Equal`, `assert.Nil`)
|
||||
|
||||
### Dependencies
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ make lint-and-fix
|
||||
|
||||
## Code generation
|
||||
|
||||
Update the generated Go code for the machine gRPC API after modifying `.proto` files:
|
||||
Update the generated Go code for the gRPC API after modifying files in [`api/pb`](./api/pb):
|
||||
|
||||
```shell
|
||||
mise run proto
|
||||
|
||||
@@ -44,6 +44,8 @@ complexity of Kubernetes.
|
||||
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
||||
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
||||
Encrypt.
|
||||
* **Cluster storage for TLS certificates**: Native cluster storage for Caddy shares certificates and ACME challenge
|
||||
tokens across machines and coordinates issuance.
|
||||
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
||||
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
||||
|
||||
@@ -154,7 +156,6 @@ Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
||||
```bash
|
||||
$ uc machine init --name oracle-vm ubuntu@152.67.101.197
|
||||
|
||||
Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh
|
||||
⏳ Running Uncloud install script...
|
||||
✓ Docker is already installed.
|
||||
⏳ Installing Docker...
|
||||
@@ -167,11 +168,10 @@ Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski
|
||||
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
||||
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
||||
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
|
||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
||||
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
|
||||
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
||||
⏳ Downloading uncloud-corrosion binary: https://github.com/psviderski/corrosion/releases/latest/download/corrosion-aarch64-unknown-linux-gnu.tar.gz
|
||||
✓ uncloud-corrosion binary installed: /usr/local/bin/uncloud-corrosion
|
||||
✓ Systemd unit file created: /etc/systemd/system/uncloud-corrosion.service
|
||||
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
||||
✓ Uncloud machine daemon started.
|
||||
✓ Uncloud installed on the machine successfully! 🎉
|
||||
@@ -190,21 +190,20 @@ DNS records updated to use only the internet-reachable machines running caddy se
|
||||
*.xuw3xd.cluster.uncloud.run A → 152.67.101.197
|
||||
```
|
||||
|
||||
1. The CLI SSHs into the machine and installs Docker, the `uncloudd` machine daemon and
|
||||
[corrosion](https://github.com/superfly/corrosion) service, managed by systemd.
|
||||
1. The CLI SSHs into the machine and installs Docker and the `uncloudd` machine daemon, managed by systemd.
|
||||
2. Generates a unique WireGuard key pair, allocates a dedicated subnet `10.210.0.0/24` for the machine and its
|
||||
containers, and configures `uncloudd` accordingly. All subsequent communication happens with `uncloudd`
|
||||
through its gRPC API over SSH.
|
||||
3. Configures and starts `corrosion`, a CRDT-based distributed SQLite database to share cluster state between machines.
|
||||
3. Configures and starts [corrosion](https://github.com/superfly/corrosion), a CRDT-based distributed SQLite database to
|
||||
share cluster state between machines, as a Docker container managed by `uncloudd`.
|
||||
4. Creates a Docker bridge network connected to the WireGuard interface.
|
||||
5. This machine becomes an entry point for the newly created cluster which is stored in the cluster config under
|
||||
`~/.config/uncloud` on your local machine.
|
||||
5. This machine becomes an entry point for the newly created cluster. Its address is stored as a connection info in the
|
||||
cluster config at `~/.config/uncloud/config.yaml` on your local machine.
|
||||
|
||||
**When you add another machine:**
|
||||
|
||||
```bash
|
||||
$ uc machine add --name hetzner-server root@5.223.45.199
|
||||
Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh
|
||||
⏳ Running Uncloud install script...
|
||||
✓ Docker is already installed.
|
||||
✓ Linux user and group 'uncloud' created.
|
||||
@@ -213,11 +212,10 @@ Downloading Uncloud install script: https://raw.githubusercontent.com/psviderski
|
||||
✓ uncloudd binary installed: /usr/local/bin/uncloudd
|
||||
⏳ Downloading uninstall script: https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/uninstall.sh
|
||||
✓ uncloud-uninstall script installed: /usr/local/bin/uncloud-uninstall
|
||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.socket
|
||||
✓ Systemd unit file created: /etc/systemd/system/uncloud.service
|
||||
Created symlink /etc/systemd/system/sockets.target.wants/uncloud.socket → /etc/systemd/system/uncloud.socket.
|
||||
Created symlink /etc/systemd/system/multi-user.target.wants/uncloud.service → /etc/systemd/system/uncloud.service.
|
||||
⏳ Downloading uncloud-corrosion binary: https://github.com/psviderski/corrosion/releases/latest/download/corrosion-x86_64-unknown-linux-gnu.tar.gz
|
||||
✓ uncloud-corrosion binary installed: /usr/local/bin/uncloud-corrosion
|
||||
✓ Systemd unit file created: /etc/systemd/system/uncloud-corrosion.service
|
||||
⏳ Starting Uncloud machine daemon (uncloud.service)...
|
||||
✓ Uncloud machine daemon started.
|
||||
✓ Uncloud installed on the machine successfully! 🎉
|
||||
@@ -246,9 +244,10 @@ hetzner-server Up 10.210.1.1/24 5.223.45.199 5.223.45.199:51820, [2
|
||||
3. Registers the second machine in the cluster state and exchanges WireGuard keys with the first machine.
|
||||
4. Both machines establish a WireGuard tunnel between each other, allowing Docker containers connected to the bridge
|
||||
network to communicate directly across machines.
|
||||
5. Configures and starts `corrosion` on the second machine to sync the cluster state.
|
||||
5. Configures and starts the `uncloud-corrosion` container on the second machine to sync the cluster state.
|
||||
6. The second machine is added as an alternative entry point in the cluster config.
|
||||
7. If one of the machines goes offline, the other machine can still serve cluster operations.
|
||||
7. If one of the machines goes offline, the other machine can still be used by `uc` to connect and run cluster
|
||||
operations.
|
||||
|
||||
If one more machine is added, the process repeats with a new subnet. The new machine needs to establish a WireGuard
|
||||
connection with only one of the existing machines. Other machines will learn about it through the shared cluster state
|
||||
@@ -326,9 +325,9 @@ I'm grateful to the following projects that inspired Uncloud's design and implem
|
||||
* [Tailscale](https://tailscale.com/) — for pioneering the vision of decentralised flat mesh networking with an amazing
|
||||
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.
|
||||
and [KubeSpan](https://www.talos.dev/v1.10/talos-guides/network/kubespan/) — for inspiring Uncloud's API routing
|
||||
design with [grpc-proxy](https://github.com/siderolabs/grpc-proxy) and for their 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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v5.27.3
|
||||
// source: internal/machine/api/pb/caddy.proto
|
||||
// source: api/pb/caddy.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -27,16 +27,18 @@ type GetCaddyConfigResponse struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The generated Caddyfile content.
|
||||
// The saved 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"`
|
||||
// Error from the latest unsuccessful reconciliation attempt.
|
||||
LastReconciliationError string `protobuf:"bytes,3,opt,name=last_reconciliation_error,json=lastReconciliationError,proto3" json:"last_reconciliation_error,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetCaddyConfigResponse) Reset() {
|
||||
*x = GetCaddyConfigResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_caddy_proto_msgTypes[0]
|
||||
mi := &file_api_pb_caddy_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -49,7 +51,7 @@ func (x *GetCaddyConfigResponse) String() string {
|
||||
func (*GetCaddyConfigResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetCaddyConfigResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_caddy_proto_msgTypes[0]
|
||||
mi := &file_api_pb_caddy_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -62,7 +64,7 @@ func (x *GetCaddyConfigResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetCaddyConfigResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetCaddyConfigResponse) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_caddy_proto_rawDescGZIP(), []int{0}
|
||||
return file_api_pb_caddy_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *GetCaddyConfigResponse) GetCaddyfile() string {
|
||||
@@ -79,53 +81,62 @@ func (x *GetCaddyConfigResponse) GetModifiedAt() *timestamppb.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_internal_machine_api_pb_caddy_proto protoreflect.FileDescriptor
|
||||
func (x *GetCaddyConfigResponse) GetLastReconciliationError() string {
|
||||
if x != nil {
|
||||
return x.LastReconciliationError
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var file_internal_machine_api_pb_caddy_proto_rawDesc = []byte{
|
||||
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,
|
||||
var File_api_pb_caddy_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_api_pb_caddy_proto_rawDesc = []byte{
|
||||
0x0a, 0x12, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaf, 0x01, 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,
|
||||
0x70, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3a, 0x0a,
|
||||
0x19, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x69, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x17, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x63, 0x69, 0x6c, 0x69, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x32, 0x49, 0x0a, 0x05, 0x43, 0x61, 0x64,
|
||||
0x64, 0x79, 0x12, 0x40, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65,
|
||||
0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e,
|
||||
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_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
|
||||
file_api_pb_caddy_proto_rawDescOnce sync.Once
|
||||
file_api_pb_caddy_proto_rawDescData = file_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)
|
||||
func file_api_pb_caddy_proto_rawDescGZIP() []byte {
|
||||
file_api_pb_caddy_proto_rawDescOnce.Do(func() {
|
||||
file_api_pb_caddy_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_caddy_proto_rawDescData)
|
||||
})
|
||||
return file_internal_machine_api_pb_caddy_proto_rawDescData
|
||||
return file_api_pb_caddy_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_machine_api_pb_caddy_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_internal_machine_api_pb_caddy_proto_goTypes = []any{
|
||||
var file_api_pb_caddy_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_api_pb_caddy_proto_goTypes = []any{
|
||||
(*GetCaddyConfigResponse)(nil), // 0: api.GetCaddyConfigResponse
|
||||
(*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp
|
||||
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
|
||||
}
|
||||
var file_internal_machine_api_pb_caddy_proto_depIdxs = []int32{
|
||||
var file_api_pb_caddy_proto_depIdxs = []int32{
|
||||
1, // 0: api.GetCaddyConfigResponse.modified_at:type_name -> google.protobuf.Timestamp
|
||||
2, // 1: api.Caddy.GetConfig:input_type -> google.protobuf.Empty
|
||||
0, // 2: api.Caddy.GetConfig:output_type -> api.GetCaddyConfigResponse
|
||||
@@ -136,13 +147,13 @@ var file_internal_machine_api_pb_caddy_proto_depIdxs = []int32{
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
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 {
|
||||
func init() { file_api_pb_caddy_proto_init() }
|
||||
func file_api_pb_caddy_proto_init() {
|
||||
if File_api_pb_caddy_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_machine_api_pb_caddy_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
file_api_pb_caddy_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*GetCaddyConfigResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -159,18 +170,18 @@ func file_internal_machine_api_pb_caddy_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_machine_api_pb_caddy_proto_rawDesc,
|
||||
RawDescriptor: file_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,
|
||||
GoTypes: file_api_pb_caddy_proto_goTypes,
|
||||
DependencyIndexes: file_api_pb_caddy_proto_depIdxs,
|
||||
MessageInfos: file_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
|
||||
File_api_pb_caddy_proto = out.File
|
||||
file_api_pb_caddy_proto_rawDesc = nil
|
||||
file_api_pb_caddy_proto_goTypes = nil
|
||||
file_api_pb_caddy_proto_depIdxs = nil
|
||||
}
|
||||
@@ -2,7 +2,7 @@ syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
@@ -13,8 +13,10 @@ service Caddy {
|
||||
}
|
||||
|
||||
message GetCaddyConfigResponse {
|
||||
// The generated Caddyfile content.
|
||||
// The saved Caddyfile content.
|
||||
string caddyfile = 1;
|
||||
// Timestamp when the config was last modified.
|
||||
google.protobuf.Timestamp modified_at = 2;
|
||||
}
|
||||
// Error from the latest unsuccessful reconciliation attempt.
|
||||
string last_reconciliation_error = 3;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.27.3
|
||||
// source: internal/machine/api/pb/caddy.proto
|
||||
// source: api/pb/caddy.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -120,5 +120,5 @@ var Caddy_ServiceDesc = grpc.ServiceDesc{
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "internal/machine/api/pb/caddy.proto",
|
||||
Metadata: "api/pb/caddy.proto",
|
||||
}
|
||||
@@ -0,0 +1,848 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v5.27.3
|
||||
// source: api/pb/caddy_storage.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type StoreCaddyStorageRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StoreCaddyStorageRequest) Reset() {
|
||||
*x = StoreCaddyStorageRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StoreCaddyStorageRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StoreCaddyStorageRequest) ProtoMessage() {}
|
||||
|
||||
func (x *StoreCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StoreCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||
func (*StoreCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *StoreCaddyStorageRequest) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StoreCaddyStorageRequest) GetValue() []byte {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LoadCaddyStorageRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
func (x *LoadCaddyStorageRequest) Reset() {
|
||||
*x = LoadCaddyStorageRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *LoadCaddyStorageRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LoadCaddyStorageRequest) ProtoMessage() {}
|
||||
|
||||
func (x *LoadCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use LoadCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||
func (*LoadCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *LoadCaddyStorageRequest) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type LoadCaddyStorageResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
func (x *LoadCaddyStorageResponse) Reset() {
|
||||
*x = LoadCaddyStorageResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *LoadCaddyStorageResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LoadCaddyStorageResponse) ProtoMessage() {}
|
||||
|
||||
func (x *LoadCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use LoadCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
||||
func (*LoadCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *LoadCaddyStorageResponse) GetValue() []byte {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *LoadCaddyStorageResponse) GetUpdatedAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.UpdatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeleteCaddyStorageRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DeleteCaddyStorageRequest) Reset() {
|
||||
*x = DeleteCaddyStorageRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DeleteCaddyStorageRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeleteCaddyStorageRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DeleteCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *DeleteCaddyStorageRequest) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListCaddyStorageRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||
Recursive bool `protobuf:"varint,2,opt,name=recursive,proto3" json:"recursive,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListCaddyStorageRequest) Reset() {
|
||||
*x = ListCaddyStorageRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListCaddyStorageRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListCaddyStorageRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *ListCaddyStorageRequest) GetPrefix() string {
|
||||
if x != nil {
|
||||
return x.Prefix
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListCaddyStorageRequest) GetRecursive() bool {
|
||||
if x != nil {
|
||||
return x.Recursive
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ListCaddyStorageResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Keys []string `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListCaddyStorageResponse) Reset() {
|
||||
*x = ListCaddyStorageResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListCaddyStorageResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListCaddyStorageResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *ListCaddyStorageResponse) GetKeys() []string {
|
||||
if x != nil {
|
||||
return x.Keys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StatCaddyStorageRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageRequest) Reset() {
|
||||
*x = StatCaddyStorageRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StatCaddyStorageRequest) ProtoMessage() {}
|
||||
|
||||
func (x *StatCaddyStorageRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StatCaddyStorageRequest.ProtoReflect.Descriptor instead.
|
||||
func (*StatCaddyStorageRequest) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageRequest) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type StatCaddyStorageResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
||||
Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
|
||||
IsTerminal bool `protobuf:"varint,4,opt,name=is_terminal,json=isTerminal,proto3" json:"is_terminal,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageResponse) Reset() {
|
||||
*x = StatCaddyStorageResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StatCaddyStorageResponse) ProtoMessage() {}
|
||||
|
||||
func (x *StatCaddyStorageResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StatCaddyStorageResponse.ProtoReflect.Descriptor instead.
|
||||
func (*StatCaddyStorageResponse) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageResponse) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageResponse) GetUpdatedAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.UpdatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageResponse) GetSize() int64 {
|
||||
if x != nil {
|
||||
return x.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *StatCaddyStorageResponse) GetIsTerminal() bool {
|
||||
if x != nil {
|
||||
return x.IsTerminal
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ListCertificatesResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Certificates []*IssuedCertificate `protobuf:"bytes,1,rep,name=certificates,proto3" json:"certificates,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ListCertificatesResponse) Reset() {
|
||||
*x = ListCertificatesResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ListCertificatesResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListCertificatesResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListCertificatesResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[8]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListCertificatesResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListCertificatesResponse) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *ListCertificatesResponse) GetCertificates() []*IssuedCertificate {
|
||||
if x != nil {
|
||||
return x.Certificates
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type IssuedCertificate struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Subject Alternative Name (SAN) of the certificate. Caddy doesn't issue certificates with multiple SANs.
|
||||
San string `protobuf:"bytes,1,opt,name=san,proto3" json:"san,omitempty"`
|
||||
// The PEM-encoding of DER-encoded ASN.1 data for the cert or chain, leaf first.
|
||||
Chain []byte `protobuf:"bytes,2,opt,name=chain,proto3" json:"chain,omitempty"`
|
||||
// Any extra information associated with the certificate, usually provided by the issuer implementation.
|
||||
// JSON-encoded, may be absent or null.
|
||||
IssuerData []byte `protobuf:"bytes,3,opt,name=issuer_data,json=issuerData,proto3" json:"issuer_data,omitempty"`
|
||||
}
|
||||
|
||||
func (x *IssuedCertificate) Reset() {
|
||||
*x = IssuedCertificate{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *IssuedCertificate) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IssuedCertificate) ProtoMessage() {}
|
||||
|
||||
func (x *IssuedCertificate) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_caddy_storage_proto_msgTypes[9]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IssuedCertificate.ProtoReflect.Descriptor instead.
|
||||
func (*IssuedCertificate) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_caddy_storage_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *IssuedCertificate) GetSan() string {
|
||||
if x != nil {
|
||||
return x.San
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *IssuedCertificate) GetChain() []byte {
|
||||
if x != nil {
|
||||
return x.Chain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *IssuedCertificate) GetIssuerData() []byte {
|
||||
if x != nil {
|
||||
return x.IssuerData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_api_pb_caddy_storage_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_api_pb_caddy_storage_proto_rawDesc = []byte{
|
||||
0x0a, 0x1a, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x61, 0x64, 0x64, 0x79, 0x5f, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70,
|
||||
0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||
0x42, 0x0a, 0x18, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
||||
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x22, 0x2b, 0x0a, 0x17, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79,
|
||||
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x22, 0x6b, 0x0a, 0x18, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
||||
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x2d, 0x0a,
|
||||
0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72,
|
||||
0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
|
||||
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4f, 0x0a, 0x17,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69,
|
||||
0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x22, 0x2e, 0x0a,
|
||||
0x18, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x2b, 0x0a,
|
||||
0x17, 0x53, 0x74, 0x61, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x9c, 0x01, 0x0a, 0x18, 0x53,
|
||||
0x74, 0x61, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x74,
|
||||
0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69,
|
||||
0x73, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0x56, 0x0a, 0x18, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||
0x61, 0x74, 0x65, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
|
||||
0x73, 0x22, 0x5c, 0x0a, 0x11, 0x49, 0x73, 0x73, 0x75, 0x65, 0x64, 0x43, 0x65, 0x72, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x61, 0x6e, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x61, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x69,
|
||||
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1f,
|
||||
0x0a, 0x0b, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x32,
|
||||
0xaa, 0x03, 0x0a, 0x0c, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
||||
0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x12, 0x43, 0x0a, 0x04, 0x4c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
|
||||
0x6f, 0x61, 0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x61,
|
||||
0x64, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12,
|
||||
0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x64, 0x64,
|
||||
0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12,
|
||||
0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53,
|
||||
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f,
|
||||
0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x04,
|
||||
0x53, 0x74, 0x61, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x43,
|
||||
0x61, 0x64, 0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x43, 0x61, 0x64,
|
||||
0x64, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x49, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||
0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64,
|
||||
0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70,
|
||||
0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_api_pb_caddy_storage_proto_rawDescOnce sync.Once
|
||||
file_api_pb_caddy_storage_proto_rawDescData = file_api_pb_caddy_storage_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_api_pb_caddy_storage_proto_rawDescGZIP() []byte {
|
||||
file_api_pb_caddy_storage_proto_rawDescOnce.Do(func() {
|
||||
file_api_pb_caddy_storage_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_caddy_storage_proto_rawDescData)
|
||||
})
|
||||
return file_api_pb_caddy_storage_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_api_pb_caddy_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||
var file_api_pb_caddy_storage_proto_goTypes = []any{
|
||||
(*StoreCaddyStorageRequest)(nil), // 0: api.StoreCaddyStorageRequest
|
||||
(*LoadCaddyStorageRequest)(nil), // 1: api.LoadCaddyStorageRequest
|
||||
(*LoadCaddyStorageResponse)(nil), // 2: api.LoadCaddyStorageResponse
|
||||
(*DeleteCaddyStorageRequest)(nil), // 3: api.DeleteCaddyStorageRequest
|
||||
(*ListCaddyStorageRequest)(nil), // 4: api.ListCaddyStorageRequest
|
||||
(*ListCaddyStorageResponse)(nil), // 5: api.ListCaddyStorageResponse
|
||||
(*StatCaddyStorageRequest)(nil), // 6: api.StatCaddyStorageRequest
|
||||
(*StatCaddyStorageResponse)(nil), // 7: api.StatCaddyStorageResponse
|
||||
(*ListCertificatesResponse)(nil), // 8: api.ListCertificatesResponse
|
||||
(*IssuedCertificate)(nil), // 9: api.IssuedCertificate
|
||||
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
||||
(*emptypb.Empty)(nil), // 11: google.protobuf.Empty
|
||||
}
|
||||
var file_api_pb_caddy_storage_proto_depIdxs = []int32{
|
||||
10, // 0: api.LoadCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
|
||||
10, // 1: api.StatCaddyStorageResponse.updated_at:type_name -> google.protobuf.Timestamp
|
||||
9, // 2: api.ListCertificatesResponse.certificates:type_name -> api.IssuedCertificate
|
||||
0, // 3: api.CaddyStorage.Store:input_type -> api.StoreCaddyStorageRequest
|
||||
1, // 4: api.CaddyStorage.Load:input_type -> api.LoadCaddyStorageRequest
|
||||
3, // 5: api.CaddyStorage.Delete:input_type -> api.DeleteCaddyStorageRequest
|
||||
4, // 6: api.CaddyStorage.List:input_type -> api.ListCaddyStorageRequest
|
||||
6, // 7: api.CaddyStorage.Stat:input_type -> api.StatCaddyStorageRequest
|
||||
11, // 8: api.CaddyStorage.ListCertificates:input_type -> google.protobuf.Empty
|
||||
11, // 9: api.CaddyStorage.Store:output_type -> google.protobuf.Empty
|
||||
2, // 10: api.CaddyStorage.Load:output_type -> api.LoadCaddyStorageResponse
|
||||
11, // 11: api.CaddyStorage.Delete:output_type -> google.protobuf.Empty
|
||||
5, // 12: api.CaddyStorage.List:output_type -> api.ListCaddyStorageResponse
|
||||
7, // 13: api.CaddyStorage.Stat:output_type -> api.StatCaddyStorageResponse
|
||||
8, // 14: api.CaddyStorage.ListCertificates:output_type -> api.ListCertificatesResponse
|
||||
9, // [9:15] is the sub-list for method output_type
|
||||
3, // [3:9] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_api_pb_caddy_storage_proto_init() }
|
||||
func file_api_pb_caddy_storage_proto_init() {
|
||||
if File_api_pb_caddy_storage_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_api_pb_caddy_storage_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*StoreCaddyStorageRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*LoadCaddyStorageRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*LoadCaddyStorageResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*DeleteCaddyStorageRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ListCaddyStorageRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ListCaddyStorageResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*StatCaddyStorageRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*StatCaddyStorageResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ListCertificatesResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_caddy_storage_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*IssuedCertificate); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_api_pb_caddy_storage_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 10,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_api_pb_caddy_storage_proto_goTypes,
|
||||
DependencyIndexes: file_api_pb_caddy_storage_proto_depIdxs,
|
||||
MessageInfos: file_api_pb_caddy_storage_proto_msgTypes,
|
||||
}.Build()
|
||||
File_api_pb_caddy_storage_proto = out.File
|
||||
file_api_pb_caddy_storage_proto_rawDesc = nil
|
||||
file_api_pb_caddy_storage_proto_goTypes = nil
|
||||
file_api_pb_caddy_storage_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
||||
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
||||
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
||||
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
||||
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
||||
service CaddyStorage {
|
||||
rpc Store(StoreCaddyStorageRequest) returns (google.protobuf.Empty);
|
||||
rpc Load(LoadCaddyStorageRequest) returns (LoadCaddyStorageResponse);
|
||||
rpc Delete(DeleteCaddyStorageRequest) returns (google.protobuf.Empty);
|
||||
rpc List(ListCaddyStorageRequest) returns (ListCaddyStorageResponse);
|
||||
rpc Stat(StatCaddyStorageRequest) returns (StatCaddyStorageResponse);
|
||||
|
||||
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
||||
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
||||
rpc ListCertificates(google.protobuf.Empty) returns (ListCertificatesResponse);
|
||||
}
|
||||
|
||||
message StoreCaddyStorageRequest {
|
||||
string key = 1;
|
||||
bytes value = 2;
|
||||
}
|
||||
|
||||
message LoadCaddyStorageRequest {
|
||||
string key = 1;
|
||||
}
|
||||
|
||||
message LoadCaddyStorageResponse {
|
||||
bytes value = 1;
|
||||
google.protobuf.Timestamp updated_at = 2;
|
||||
}
|
||||
|
||||
message DeleteCaddyStorageRequest {
|
||||
string key = 1;
|
||||
}
|
||||
|
||||
message ListCaddyStorageRequest {
|
||||
string prefix = 1;
|
||||
bool recursive = 2;
|
||||
}
|
||||
|
||||
message ListCaddyStorageResponse {
|
||||
repeated string keys = 1;
|
||||
}
|
||||
|
||||
message StatCaddyStorageRequest {
|
||||
string key = 1;
|
||||
}
|
||||
|
||||
message StatCaddyStorageResponse {
|
||||
string key = 1;
|
||||
google.protobuf.Timestamp updated_at = 2;
|
||||
int64 size = 3;
|
||||
bool is_terminal = 4;
|
||||
}
|
||||
|
||||
message ListCertificatesResponse {
|
||||
repeated IssuedCertificate certificates = 1;
|
||||
}
|
||||
|
||||
message IssuedCertificate {
|
||||
// Subject Alternative Name (SAN) of the certificate. Caddy doesn't issue certificates with multiple SANs.
|
||||
string san = 1;
|
||||
// The PEM-encoding of DER-encoded ASN.1 data for the cert or chain, leaf first.
|
||||
bytes chain = 2;
|
||||
// Any extra information associated with the certificate, usually provided by the issuer implementation.
|
||||
// JSON-encoded, may be absent or null.
|
||||
bytes issuer_data = 3;
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.27.3
|
||||
// source: api/pb/caddy_storage.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
CaddyStorage_Store_FullMethodName = "/api.CaddyStorage/Store"
|
||||
CaddyStorage_Load_FullMethodName = "/api.CaddyStorage/Load"
|
||||
CaddyStorage_Delete_FullMethodName = "/api.CaddyStorage/Delete"
|
||||
CaddyStorage_List_FullMethodName = "/api.CaddyStorage/List"
|
||||
CaddyStorage_Stat_FullMethodName = "/api.CaddyStorage/Stat"
|
||||
CaddyStorage_ListCertificates_FullMethodName = "/api.CaddyStorage/ListCertificates"
|
||||
)
|
||||
|
||||
// CaddyStorageClient is the client API for CaddyStorage service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
||||
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
||||
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
||||
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
||||
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
||||
type CaddyStorageClient interface {
|
||||
Store(ctx context.Context, in *StoreCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
Load(ctx context.Context, in *LoadCaddyStorageRequest, opts ...grpc.CallOption) (*LoadCaddyStorageResponse, error)
|
||||
Delete(ctx context.Context, in *DeleteCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
List(ctx context.Context, in *ListCaddyStorageRequest, opts ...grpc.CallOption) (*ListCaddyStorageResponse, error)
|
||||
Stat(ctx context.Context, in *StatCaddyStorageRequest, opts ...grpc.CallOption) (*StatCaddyStorageResponse, error)
|
||||
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
||||
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
||||
ListCertificates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListCertificatesResponse, error)
|
||||
}
|
||||
|
||||
type caddyStorageClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewCaddyStorageClient(cc grpc.ClientConnInterface) CaddyStorageClient {
|
||||
return &caddyStorageClient{cc}
|
||||
}
|
||||
|
||||
func (c *caddyStorageClient) Store(ctx context.Context, in *StoreCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, CaddyStorage_Store_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *caddyStorageClient) Load(ctx context.Context, in *LoadCaddyStorageRequest, opts ...grpc.CallOption) (*LoadCaddyStorageResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(LoadCaddyStorageResponse)
|
||||
err := c.cc.Invoke(ctx, CaddyStorage_Load_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *caddyStorageClient) Delete(ctx context.Context, in *DeleteCaddyStorageRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, CaddyStorage_Delete_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *caddyStorageClient) List(ctx context.Context, in *ListCaddyStorageRequest, opts ...grpc.CallOption) (*ListCaddyStorageResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListCaddyStorageResponse)
|
||||
err := c.cc.Invoke(ctx, CaddyStorage_List_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *caddyStorageClient) Stat(ctx context.Context, in *StatCaddyStorageRequest, opts ...grpc.CallOption) (*StatCaddyStorageResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(StatCaddyStorageResponse)
|
||||
err := c.cc.Invoke(ctx, CaddyStorage_Stat_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *caddyStorageClient) ListCertificates(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListCertificatesResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListCertificatesResponse)
|
||||
err := c.cc.Invoke(ctx, CaddyStorage_ListCertificates_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CaddyStorageServer is the server API for CaddyStorage service.
|
||||
// All implementations must embed UnimplementedCaddyStorageServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// CaddyStorage exposes the CertMagic storage operations backed by the distributed cluster store.
|
||||
// Each request operates on the receiving machine's store replica. Callers that need replication to catch up before
|
||||
// reading should capture store versions with Machine.InspectMachine and call Machine.WaitForStoreVersion on the
|
||||
// receiving machine, subject to that RPC's data-availability limitations (see WaitForStoreVersion docs).
|
||||
// See Storage interface in https://github.com/caddyserver/certmagic/blob/master/storage.go.
|
||||
type CaddyStorageServer interface {
|
||||
Store(context.Context, *StoreCaddyStorageRequest) (*emptypb.Empty, error)
|
||||
Load(context.Context, *LoadCaddyStorageRequest) (*LoadCaddyStorageResponse, error)
|
||||
Delete(context.Context, *DeleteCaddyStorageRequest) (*emptypb.Empty, error)
|
||||
List(context.Context, *ListCaddyStorageRequest) (*ListCaddyStorageResponse, error)
|
||||
Stat(context.Context, *StatCaddyStorageRequest) (*StatCaddyStorageResponse, error)
|
||||
// ListCertificates lists certificates issued by Caddy and stored in the cluster store.
|
||||
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
||||
ListCertificates(context.Context, *emptypb.Empty) (*ListCertificatesResponse, error)
|
||||
mustEmbedUnimplementedCaddyStorageServer()
|
||||
}
|
||||
|
||||
// UnimplementedCaddyStorageServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedCaddyStorageServer struct{}
|
||||
|
||||
func (UnimplementedCaddyStorageServer) Store(context.Context, *StoreCaddyStorageRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Store not implemented")
|
||||
}
|
||||
func (UnimplementedCaddyStorageServer) Load(context.Context, *LoadCaddyStorageRequest) (*LoadCaddyStorageResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Load not implemented")
|
||||
}
|
||||
func (UnimplementedCaddyStorageServer) Delete(context.Context, *DeleteCaddyStorageRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented")
|
||||
}
|
||||
func (UnimplementedCaddyStorageServer) List(context.Context, *ListCaddyStorageRequest) (*ListCaddyStorageResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||
}
|
||||
func (UnimplementedCaddyStorageServer) Stat(context.Context, *StatCaddyStorageRequest) (*StatCaddyStorageResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Stat not implemented")
|
||||
}
|
||||
func (UnimplementedCaddyStorageServer) ListCertificates(context.Context, *emptypb.Empty) (*ListCertificatesResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListCertificates not implemented")
|
||||
}
|
||||
func (UnimplementedCaddyStorageServer) mustEmbedUnimplementedCaddyStorageServer() {}
|
||||
func (UnimplementedCaddyStorageServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeCaddyStorageServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to CaddyStorageServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeCaddyStorageServer interface {
|
||||
mustEmbedUnimplementedCaddyStorageServer()
|
||||
}
|
||||
|
||||
func RegisterCaddyStorageServer(s grpc.ServiceRegistrar, srv CaddyStorageServer) {
|
||||
// If the following call pancis, it indicates UnimplementedCaddyStorageServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&CaddyStorage_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _CaddyStorage_Store_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StoreCaddyStorageRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CaddyStorageServer).Store(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CaddyStorage_Store_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CaddyStorageServer).Store(ctx, req.(*StoreCaddyStorageRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CaddyStorage_Load_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LoadCaddyStorageRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CaddyStorageServer).Load(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CaddyStorage_Load_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CaddyStorageServer).Load(ctx, req.(*LoadCaddyStorageRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CaddyStorage_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteCaddyStorageRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CaddyStorageServer).Delete(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CaddyStorage_Delete_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CaddyStorageServer).Delete(ctx, req.(*DeleteCaddyStorageRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CaddyStorage_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListCaddyStorageRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CaddyStorageServer).List(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CaddyStorage_List_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CaddyStorageServer).List(ctx, req.(*ListCaddyStorageRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CaddyStorage_Stat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(StatCaddyStorageRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CaddyStorageServer).Stat(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CaddyStorage_Stat_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CaddyStorageServer).Stat(ctx, req.(*StatCaddyStorageRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _CaddyStorage_ListCertificates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CaddyStorageServer).ListCertificates(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CaddyStorage_ListCertificates_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CaddyStorageServer).ListCertificates(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// CaddyStorage_ServiceDesc is the grpc.ServiceDesc for CaddyStorage service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var CaddyStorage_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "api.CaddyStorage",
|
||||
HandlerType: (*CaddyStorageServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Store",
|
||||
Handler: _CaddyStorage_Store_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Load",
|
||||
Handler: _CaddyStorage_Load_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Delete",
|
||||
Handler: _CaddyStorage_Delete_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _CaddyStorage_List_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Stat",
|
||||
Handler: _CaddyStorage_Stat_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListCertificates",
|
||||
Handler: _CaddyStorage_ListCertificates_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "api/pb/caddy_storage.proto",
|
||||
}
|
||||
File renamed without changes.
@@ -2,7 +2,7 @@
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v5.27.3
|
||||
// source: internal/machine/api/pb/cluster.proto
|
||||
// source: api/pb/cluster.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -62,11 +62,11 @@ func (x MachineMember_MembershipState) String() string {
|
||||
}
|
||||
|
||||
func (MachineMember_MembershipState) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_machine_api_pb_cluster_proto_enumTypes[0].Descriptor()
|
||||
return file_api_pb_cluster_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (MachineMember_MembershipState) Type() protoreflect.EnumType {
|
||||
return &file_internal_machine_api_pb_cluster_proto_enumTypes[0]
|
||||
return &file_api_pb_cluster_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x MachineMember_MembershipState) Number() protoreflect.EnumNumber {
|
||||
@@ -75,7 +75,7 @@ func (x MachineMember_MembershipState) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use MachineMember_MembershipState.Descriptor instead.
|
||||
func (MachineMember_MembershipState) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{2, 0}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{2, 0}
|
||||
}
|
||||
|
||||
type DNSRecord_RecordType int32
|
||||
@@ -111,11 +111,11 @@ func (x DNSRecord_RecordType) String() string {
|
||||
}
|
||||
|
||||
func (DNSRecord_RecordType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_machine_api_pb_cluster_proto_enumTypes[1].Descriptor()
|
||||
return file_api_pb_cluster_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (DNSRecord_RecordType) Type() protoreflect.EnumType {
|
||||
return &file_internal_machine_api_pb_cluster_proto_enumTypes[1]
|
||||
return &file_api_pb_cluster_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x DNSRecord_RecordType) Number() protoreflect.EnumNumber {
|
||||
@@ -124,7 +124,7 @@ func (x DNSRecord_RecordType) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use DNSRecord_RecordType.Descriptor instead.
|
||||
func (DNSRecord_RecordType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{9, 0}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{10, 0}
|
||||
}
|
||||
|
||||
type AddMachineRequest struct {
|
||||
@@ -140,7 +140,7 @@ type AddMachineRequest struct {
|
||||
func (x *AddMachineRequest) Reset() {
|
||||
*x = AddMachineRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[0]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func (x *AddMachineRequest) String() string {
|
||||
func (*AddMachineRequest) ProtoMessage() {}
|
||||
|
||||
func (x *AddMachineRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[0]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -166,7 +166,7 @@ func (x *AddMachineRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use AddMachineRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AddMachineRequest) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{0}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *AddMachineRequest) GetName() string {
|
||||
@@ -201,7 +201,7 @@ type AddMachineResponse struct {
|
||||
func (x *AddMachineResponse) Reset() {
|
||||
*x = AddMachineResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[1]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -214,7 +214,7 @@ func (x *AddMachineResponse) String() string {
|
||||
func (*AddMachineResponse) ProtoMessage() {}
|
||||
|
||||
func (x *AddMachineResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[1]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -227,7 +227,7 @@ func (x *AddMachineResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use AddMachineResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AddMachineResponse) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{1}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *AddMachineResponse) GetMachine() *MachineInfo {
|
||||
@@ -249,7 +249,7 @@ type MachineMember struct {
|
||||
func (x *MachineMember) Reset() {
|
||||
*x = MachineMember{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[2]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -262,7 +262,7 @@ func (x *MachineMember) String() string {
|
||||
func (*MachineMember) ProtoMessage() {}
|
||||
|
||||
func (x *MachineMember) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[2]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -275,7 +275,7 @@ func (x *MachineMember) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MachineMember.ProtoReflect.Descriptor instead.
|
||||
func (*MachineMember) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{2}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *MachineMember) GetMachine() *MachineInfo {
|
||||
@@ -303,7 +303,7 @@ type ListMachinesResponse struct {
|
||||
func (x *ListMachinesResponse) Reset() {
|
||||
*x = ListMachinesResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[3]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -316,7 +316,7 @@ func (x *ListMachinesResponse) String() string {
|
||||
func (*ListMachinesResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListMachinesResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[3]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -329,7 +329,7 @@ func (x *ListMachinesResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ListMachinesResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListMachinesResponse) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{3}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *ListMachinesResponse) GetMachines() []*MachineMember {
|
||||
@@ -350,7 +350,7 @@ type RemoveMachineRequest struct {
|
||||
func (x *RemoveMachineRequest) Reset() {
|
||||
*x = RemoveMachineRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[4]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -363,7 +363,7 @@ func (x *RemoveMachineRequest) String() string {
|
||||
func (*RemoveMachineRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RemoveMachineRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[4]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -376,7 +376,7 @@ func (x *RemoveMachineRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RemoveMachineRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RemoveMachineRequest) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{4}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *RemoveMachineRequest) GetId() string {
|
||||
@@ -392,12 +392,15 @@ type Domain struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
// Whether the domain is reserved in Uncloud DNS. Older daemons (<0.21) omit this field
|
||||
// and only support reserved domains, so clients should treat absence as true.
|
||||
Reserved *bool `protobuf:"varint,2,opt,name=reserved,proto3,oneof" json:"reserved,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Domain) Reset() {
|
||||
*x = Domain{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[5]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -410,7 +413,7 @@ func (x *Domain) String() string {
|
||||
func (*Domain) ProtoMessage() {}
|
||||
|
||||
func (x *Domain) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[5]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -423,7 +426,7 @@ func (x *Domain) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
|
||||
func (*Domain) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{5}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *Domain) GetName() string {
|
||||
@@ -433,6 +436,13 @@ func (x *Domain) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Domain) GetReserved() bool {
|
||||
if x != nil && x.Reserved != nil {
|
||||
return *x.Reserved
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ReserveDomainRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -444,7 +454,7 @@ type ReserveDomainRequest struct {
|
||||
func (x *ReserveDomainRequest) Reset() {
|
||||
*x = ReserveDomainRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[6]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -457,7 +467,7 @@ func (x *ReserveDomainRequest) String() string {
|
||||
func (*ReserveDomainRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ReserveDomainRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[6]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -470,7 +480,7 @@ func (x *ReserveDomainRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ReserveDomainRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ReserveDomainRequest) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{6}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *ReserveDomainRequest) GetEndpoint() string {
|
||||
@@ -480,6 +490,54 @@ func (x *ReserveDomainRequest) GetEndpoint() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type SetDomainRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// An externally managed domain name. Empty clears a manually set domain.
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SetDomainRequest) Reset() {
|
||||
*x = SetDomainRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SetDomainRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SetDomainRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SetDomainRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SetDomainRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SetDomainRequest) Descriptor() ([]byte, []int) {
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *SetDomainRequest) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateDomainRecordsRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -491,7 +549,7 @@ type CreateDomainRecordsRequest struct {
|
||||
func (x *CreateDomainRecordsRequest) Reset() {
|
||||
*x = CreateDomainRecordsRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[7]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -504,7 +562,7 @@ func (x *CreateDomainRecordsRequest) String() string {
|
||||
func (*CreateDomainRecordsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[7]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[8]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -517,7 +575,7 @@ func (x *CreateDomainRecordsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use CreateDomainRecordsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CreateDomainRecordsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{7}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *CreateDomainRecordsRequest) GetRecords() []*DNSRecord {
|
||||
@@ -538,7 +596,7 @@ type CreateDomainRecordsResponse struct {
|
||||
func (x *CreateDomainRecordsResponse) Reset() {
|
||||
*x = CreateDomainRecordsResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[8]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -551,7 +609,7 @@ func (x *CreateDomainRecordsResponse) String() string {
|
||||
func (*CreateDomainRecordsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[8]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[9]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -564,7 +622,7 @@ func (x *CreateDomainRecordsResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use CreateDomainRecordsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*CreateDomainRecordsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{8}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *CreateDomainRecordsResponse) GetRecords() []*DNSRecord {
|
||||
@@ -587,7 +645,7 @@ type DNSRecord struct {
|
||||
func (x *DNSRecord) Reset() {
|
||||
*x = DNSRecord{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[9]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -600,7 +658,7 @@ func (x *DNSRecord) String() string {
|
||||
func (*DNSRecord) ProtoMessage() {}
|
||||
|
||||
func (x *DNSRecord) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_cluster_proto_msgTypes[9]
|
||||
mi := &file_api_pb_cluster_proto_msgTypes[10]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -613,7 +671,7 @@ func (x *DNSRecord) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use DNSRecord.ProtoReflect.Descriptor instead.
|
||||
func (*DNSRecord) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescGZIP(), []int{9}
|
||||
return file_api_pb_cluster_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *DNSRecord) GetName() string {
|
||||
@@ -637,123 +695,128 @@ func (x *DNSRecord) GetValues() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_internal_machine_api_pb_cluster_proto protoreflect.FileDescriptor
|
||||
var File_api_pb_cluster_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_machine_api_pb_cluster_proto_rawDesc = []byte{
|
||||
0x0a, 0x25, 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, 0x6c, 0x75, 0x73, 0x74, 0x65,
|
||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
||||
0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
|
||||
0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||
0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63,
|
||||
0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x2c, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x24, 0x0a,
|
||||
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69,
|
||||
0x63, 0x49, 0x70, 0x22, 0x40, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63,
|
||||
0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61,
|
||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0xb4, 0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||
0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||
0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d,
|
||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68,
|
||||
0x69, 0x6e, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||
0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69,
|
||||
0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3d, 0x0a,
|
||||
0x0f, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65,
|
||||
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x06, 0x0a,
|
||||
0x02, 0x55, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x53, 0x50, 0x45, 0x43, 0x54,
|
||||
0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x14,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63,
|
||||
0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68,
|
||||
0x69, 0x6e, 0x65, 0x73, 0x22, 0x26, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61,
|
||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x06,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x46,
|
||||
0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65,
|
||||
0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x07,
|
||||
0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72,
|
||||
0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
|
||||
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53,
|
||||
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22,
|
||||
0x96, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a,
|
||||
var file_api_pb_cluster_proto_rawDesc = []byte{
|
||||
0x0a, 0x14, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62,
|
||||
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x61,
|
||||
0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0x7b, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07,
|
||||
0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x75,
|
||||
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70,
|
||||
0x22, 0x40, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61,
|
||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||
0x6e, 0x65, 0x22, 0xb4, 0x01, 0x0a, 0x0d, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65,
|
||||
0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68,
|
||||
0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||
0x12, 0x38, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x6d,
|
||||
0x62, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3d, 0x0a, 0x0f, 0x4d, 0x65,
|
||||
0x6d, 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x55, 0x50,
|
||||
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x53, 0x50, 0x45, 0x43, 0x54, 0x10, 0x02, 0x12,
|
||||
0x08, 0x0a, 0x04, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x46, 0x0a, 0x14, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x2e, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||
0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||
0x73, 0x22, 0x26, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||
0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4a, 0x0a, 0x06, 0x44, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x65, 0x72,
|
||||
0x76, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x72, 0x65, 0x73,
|
||||
0x65, 0x72, 0x76, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x72, 0x65, 0x73,
|
||||
0x65, 0x72, 0x76, 0x65, 0x64, 0x22, 0x32, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x26, 0x0a, 0x10, 0x53, 0x65, 0x74,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2e,
|
||||
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
|
||||
0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f,
|
||||
0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,
|
||||
0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x41, 0x10, 0x01, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0xca, 0x03, 0x0a, 0x07, 0x43, 0x6c, 0x75,
|
||||
0x73, 0x74, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||
0x6e, 0x65, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68,
|
||||
0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x41, 0x64, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||
0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
|
||||
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65,
|
||||
0x6d, 0x6f, 0x76, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a, 0x0d, 0x52, 0x65,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b,
|
||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72,
|
||||
0x64, 0x73, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 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,
|
||||
0x65, 0x22, 0x46, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||
0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x47, 0x0a, 0x1b, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x6f,
|
||||
0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72,
|
||||
0x64, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x09, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x4e, 0x53, 0x52, 0x65, 0x63, 0x6f,
|
||||
0x72, 0x64, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
|
||||
0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20,
|
||||
0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x2e, 0x0a, 0x0a, 0x52,
|
||||
0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53,
|
||||
0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x05, 0x0a, 0x01, 0x41, 0x10,
|
||||
0x01, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x41, 0x41, 0x41, 0x10, 0x02, 0x32, 0x86, 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, 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, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 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, 0x30, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
|
||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x58, 0x0a, 0x13, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||
0x73, 0x12, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f,
|
||||
0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44,
|
||||
0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e,
|
||||
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_machine_api_pb_cluster_proto_rawDescOnce sync.Once
|
||||
file_internal_machine_api_pb_cluster_proto_rawDescData = file_internal_machine_api_pb_cluster_proto_rawDesc
|
||||
file_api_pb_cluster_proto_rawDescOnce sync.Once
|
||||
file_api_pb_cluster_proto_rawDescData = file_api_pb_cluster_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_machine_api_pb_cluster_proto_rawDescGZIP() []byte {
|
||||
file_internal_machine_api_pb_cluster_proto_rawDescOnce.Do(func() {
|
||||
file_internal_machine_api_pb_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_machine_api_pb_cluster_proto_rawDescData)
|
||||
func file_api_pb_cluster_proto_rawDescGZIP() []byte {
|
||||
file_api_pb_cluster_proto_rawDescOnce.Do(func() {
|
||||
file_api_pb_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_cluster_proto_rawDescData)
|
||||
})
|
||||
return file_internal_machine_api_pb_cluster_proto_rawDescData
|
||||
return file_api_pb_cluster_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_machine_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_internal_machine_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||
var file_internal_machine_api_pb_cluster_proto_goTypes = []any{
|
||||
var file_api_pb_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_api_pb_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||
var file_api_pb_cluster_proto_goTypes = []any{
|
||||
(MachineMember_MembershipState)(0), // 0: api.MachineMember.MembershipState
|
||||
(DNSRecord_RecordType)(0), // 1: api.DNSRecord.RecordType
|
||||
(*AddMachineRequest)(nil), // 2: api.AddMachineRequest
|
||||
@@ -763,54 +826,57 @@ var file_internal_machine_api_pb_cluster_proto_goTypes = []any{
|
||||
(*RemoveMachineRequest)(nil), // 6: api.RemoveMachineRequest
|
||||
(*Domain)(nil), // 7: api.Domain
|
||||
(*ReserveDomainRequest)(nil), // 8: api.ReserveDomainRequest
|
||||
(*CreateDomainRecordsRequest)(nil), // 9: api.CreateDomainRecordsRequest
|
||||
(*CreateDomainRecordsResponse)(nil), // 10: api.CreateDomainRecordsResponse
|
||||
(*DNSRecord)(nil), // 11: api.DNSRecord
|
||||
(*NetworkConfig)(nil), // 12: api.NetworkConfig
|
||||
(*IP)(nil), // 13: api.IP
|
||||
(*MachineInfo)(nil), // 14: api.MachineInfo
|
||||
(*emptypb.Empty)(nil), // 15: google.protobuf.Empty
|
||||
(*SetDomainRequest)(nil), // 9: api.SetDomainRequest
|
||||
(*CreateDomainRecordsRequest)(nil), // 10: api.CreateDomainRecordsRequest
|
||||
(*CreateDomainRecordsResponse)(nil), // 11: api.CreateDomainRecordsResponse
|
||||
(*DNSRecord)(nil), // 12: api.DNSRecord
|
||||
(*NetworkConfig)(nil), // 13: api.NetworkConfig
|
||||
(*IP)(nil), // 14: api.IP
|
||||
(*MachineInfo)(nil), // 15: api.MachineInfo
|
||||
(*emptypb.Empty)(nil), // 16: google.protobuf.Empty
|
||||
}
|
||||
var file_internal_machine_api_pb_cluster_proto_depIdxs = []int32{
|
||||
12, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig
|
||||
13, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP
|
||||
14, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo
|
||||
14, // 3: api.MachineMember.machine:type_name -> api.MachineInfo
|
||||
var file_api_pb_cluster_proto_depIdxs = []int32{
|
||||
13, // 0: api.AddMachineRequest.network:type_name -> api.NetworkConfig
|
||||
14, // 1: api.AddMachineRequest.public_ip:type_name -> api.IP
|
||||
15, // 2: api.AddMachineResponse.machine:type_name -> api.MachineInfo
|
||||
15, // 3: api.MachineMember.machine:type_name -> api.MachineInfo
|
||||
0, // 4: api.MachineMember.state:type_name -> api.MachineMember.MembershipState
|
||||
4, // 5: api.ListMachinesResponse.machines:type_name -> api.MachineMember
|
||||
11, // 6: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord
|
||||
11, // 7: api.CreateDomainRecordsResponse.records:type_name -> api.DNSRecord
|
||||
12, // 6: api.CreateDomainRecordsRequest.records:type_name -> api.DNSRecord
|
||||
12, // 7: api.CreateDomainRecordsResponse.records:type_name -> api.DNSRecord
|
||||
1, // 8: api.DNSRecord.type:type_name -> api.DNSRecord.RecordType
|
||||
2, // 9: api.Cluster.AddMachine:input_type -> api.AddMachineRequest
|
||||
15, // 10: api.Cluster.ListMachines:input_type -> google.protobuf.Empty
|
||||
16, // 10: api.Cluster.ListMachines:input_type -> google.protobuf.Empty
|
||||
6, // 11: api.Cluster.RemoveMachine:input_type -> api.RemoveMachineRequest
|
||||
8, // 12: api.Cluster.ReserveDomain:input_type -> api.ReserveDomainRequest
|
||||
15, // 13: api.Cluster.GetDomain:input_type -> google.protobuf.Empty
|
||||
15, // 14: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty
|
||||
9, // 15: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest
|
||||
3, // 16: api.Cluster.AddMachine:output_type -> api.AddMachineResponse
|
||||
5, // 17: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse
|
||||
15, // 18: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty
|
||||
7, // 19: api.Cluster.ReserveDomain:output_type -> api.Domain
|
||||
7, // 20: api.Cluster.GetDomain:output_type -> api.Domain
|
||||
7, // 21: api.Cluster.ReleaseDomain:output_type -> api.Domain
|
||||
10, // 22: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
|
||||
16, // [16:23] is the sub-list for method output_type
|
||||
9, // [9:16] is the sub-list for method input_type
|
||||
9, // 13: api.Cluster.SetDomain:input_type -> api.SetDomainRequest
|
||||
16, // 14: api.Cluster.GetDomain:input_type -> google.protobuf.Empty
|
||||
16, // 15: api.Cluster.ReleaseDomain:input_type -> google.protobuf.Empty
|
||||
10, // 16: api.Cluster.CreateDomainRecords:input_type -> api.CreateDomainRecordsRequest
|
||||
3, // 17: api.Cluster.AddMachine:output_type -> api.AddMachineResponse
|
||||
5, // 18: api.Cluster.ListMachines:output_type -> api.ListMachinesResponse
|
||||
16, // 19: api.Cluster.RemoveMachine:output_type -> google.protobuf.Empty
|
||||
7, // 20: api.Cluster.ReserveDomain:output_type -> api.Domain
|
||||
16, // 21: api.Cluster.SetDomain:output_type -> google.protobuf.Empty
|
||||
7, // 22: api.Cluster.GetDomain:output_type -> api.Domain
|
||||
7, // 23: api.Cluster.ReleaseDomain:output_type -> api.Domain
|
||||
11, // 24: api.Cluster.CreateDomainRecords:output_type -> api.CreateDomainRecordsResponse
|
||||
17, // [17:25] is the sub-list for method output_type
|
||||
9, // [9:17] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_machine_api_pb_cluster_proto_init() }
|
||||
func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
if File_internal_machine_api_pb_cluster_proto != nil {
|
||||
func init() { file_api_pb_cluster_proto_init() }
|
||||
func file_api_pb_cluster_proto_init() {
|
||||
if File_api_pb_cluster_proto != nil {
|
||||
return
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_init()
|
||||
file_internal_machine_api_pb_machine_proto_init()
|
||||
file_api_pb_common_proto_init()
|
||||
file_api_pb_machine_proto_init()
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*AddMachineRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -822,7 +888,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*AddMachineResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -834,7 +900,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*MachineMember); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -846,7 +912,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ListMachinesResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -858,7 +924,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*RemoveMachineRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -870,7 +936,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Domain); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -882,7 +948,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*ReserveDomainRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -894,7 +960,19 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*SetDomainRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_api_pb_cluster_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*CreateDomainRecordsRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -906,7 +984,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*CreateDomainRecordsResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -918,7 +996,7 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_cluster_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
||||
file_api_pb_cluster_proto_msgTypes[10].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*DNSRecord); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -931,23 +1009,24 @@ func file_internal_machine_api_pb_cluster_proto_init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
file_api_pb_cluster_proto_msgTypes[5].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_machine_api_pb_cluster_proto_rawDesc,
|
||||
RawDescriptor: file_api_pb_cluster_proto_rawDesc,
|
||||
NumEnums: 2,
|
||||
NumMessages: 10,
|
||||
NumMessages: 11,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_internal_machine_api_pb_cluster_proto_goTypes,
|
||||
DependencyIndexes: file_internal_machine_api_pb_cluster_proto_depIdxs,
|
||||
EnumInfos: file_internal_machine_api_pb_cluster_proto_enumTypes,
|
||||
MessageInfos: file_internal_machine_api_pb_cluster_proto_msgTypes,
|
||||
GoTypes: file_api_pb_cluster_proto_goTypes,
|
||||
DependencyIndexes: file_api_pb_cluster_proto_depIdxs,
|
||||
EnumInfos: file_api_pb_cluster_proto_enumTypes,
|
||||
MessageInfos: file_api_pb_cluster_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_machine_api_pb_cluster_proto = out.File
|
||||
file_internal_machine_api_pb_cluster_proto_rawDesc = nil
|
||||
file_internal_machine_api_pb_cluster_proto_goTypes = nil
|
||||
file_internal_machine_api_pb_cluster_proto_depIdxs = nil
|
||||
File_api_pb_cluster_proto = out.File
|
||||
file_api_pb_cluster_proto_rawDesc = nil
|
||||
file_api_pb_cluster_proto_goTypes = nil
|
||||
file_api_pb_cluster_proto_depIdxs = nil
|
||||
}
|
||||
@@ -2,11 +2,11 @@ syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "internal/machine/api/pb/common.proto";
|
||||
import "internal/machine/api/pb/machine.proto";
|
||||
import "api/pb/common.proto";
|
||||
import "api/pb/machine.proto";
|
||||
|
||||
service Cluster {
|
||||
rpc AddMachine(AddMachineRequest) returns (AddMachineResponse);
|
||||
@@ -14,6 +14,7 @@ service Cluster {
|
||||
rpc RemoveMachine(RemoveMachineRequest) returns (google.protobuf.Empty);
|
||||
|
||||
rpc ReserveDomain(ReserveDomainRequest) returns (Domain);
|
||||
rpc SetDomain(SetDomainRequest) returns (google.protobuf.Empty);
|
||||
rpc GetDomain(google.protobuf.Empty) returns (Domain);
|
||||
rpc ReleaseDomain(google.protobuf.Empty) returns (Domain);
|
||||
rpc CreateDomainRecords(CreateDomainRecordsRequest) returns (CreateDomainRecordsResponse);
|
||||
@@ -56,12 +57,20 @@ message RemoveMachineRequest {
|
||||
|
||||
message Domain {
|
||||
string name = 1;
|
||||
// Whether the domain is reserved in Uncloud DNS. Older daemons (<0.21) omit this field
|
||||
// and only support reserved domains, so clients should treat absence as true.
|
||||
optional bool reserved = 2;
|
||||
}
|
||||
|
||||
message ReserveDomainRequest {
|
||||
string endpoint = 1;
|
||||
}
|
||||
|
||||
message SetDomainRequest {
|
||||
// An externally managed domain name. Empty clears a manually set domain.
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message CreateDomainRecordsRequest {
|
||||
repeated DNSRecord records = 1;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.27.3
|
||||
// source: internal/machine/api/pb/cluster.proto
|
||||
// source: api/pb/cluster.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -24,6 +24,7 @@ const (
|
||||
Cluster_ListMachines_FullMethodName = "/api.Cluster/ListMachines"
|
||||
Cluster_RemoveMachine_FullMethodName = "/api.Cluster/RemoveMachine"
|
||||
Cluster_ReserveDomain_FullMethodName = "/api.Cluster/ReserveDomain"
|
||||
Cluster_SetDomain_FullMethodName = "/api.Cluster/SetDomain"
|
||||
Cluster_GetDomain_FullMethodName = "/api.Cluster/GetDomain"
|
||||
Cluster_ReleaseDomain_FullMethodName = "/api.Cluster/ReleaseDomain"
|
||||
Cluster_CreateDomainRecords_FullMethodName = "/api.Cluster/CreateDomainRecords"
|
||||
@@ -37,6 +38,7 @@ type ClusterClient interface {
|
||||
ListMachines(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListMachinesResponse, error)
|
||||
RemoveMachine(ctx context.Context, in *RemoveMachineRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
ReserveDomain(ctx context.Context, in *ReserveDomainRequest, opts ...grpc.CallOption) (*Domain, error)
|
||||
SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
|
||||
ReleaseDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error)
|
||||
CreateDomainRecords(ctx context.Context, in *CreateDomainRecordsRequest, opts ...grpc.CallOption) (*CreateDomainRecordsResponse, error)
|
||||
@@ -90,6 +92,16 @@ func (c *clusterClient) ReserveDomain(ctx context.Context, in *ReserveDomainRequ
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clusterClient) SetDomain(ctx context.Context, in *SetDomainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, Cluster_SetDomain_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clusterClient) GetDomain(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Domain, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(Domain)
|
||||
@@ -128,6 +140,7 @@ type ClusterServer interface {
|
||||
ListMachines(context.Context, *emptypb.Empty) (*ListMachinesResponse, error)
|
||||
RemoveMachine(context.Context, *RemoveMachineRequest) (*emptypb.Empty, error)
|
||||
ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error)
|
||||
SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error)
|
||||
GetDomain(context.Context, *emptypb.Empty) (*Domain, error)
|
||||
ReleaseDomain(context.Context, *emptypb.Empty) (*Domain, error)
|
||||
CreateDomainRecords(context.Context, *CreateDomainRecordsRequest) (*CreateDomainRecordsResponse, error)
|
||||
@@ -153,6 +166,9 @@ func (UnimplementedClusterServer) RemoveMachine(context.Context, *RemoveMachineR
|
||||
func (UnimplementedClusterServer) ReserveDomain(context.Context, *ReserveDomainRequest) (*Domain, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ReserveDomain not implemented")
|
||||
}
|
||||
func (UnimplementedClusterServer) SetDomain(context.Context, *SetDomainRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SetDomain not implemented")
|
||||
}
|
||||
func (UnimplementedClusterServer) GetDomain(context.Context, *emptypb.Empty) (*Domain, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetDomain not implemented")
|
||||
}
|
||||
@@ -255,6 +271,24 @@ func _Cluster_ReserveDomain_Handler(srv interface{}, ctx context.Context, dec fu
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Cluster_SetDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SetDomainRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClusterServer).SetDomain(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Cluster_SetDomain_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClusterServer).SetDomain(ctx, req.(*SetDomainRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Cluster_GetDomain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -332,6 +366,10 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ReserveDomain",
|
||||
Handler: _Cluster_ReserveDomain_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SetDomain",
|
||||
Handler: _Cluster_SetDomain_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetDomain",
|
||||
Handler: _Cluster_GetDomain_Handler,
|
||||
@@ -346,5 +384,5 @@ var Cluster_ServiceDesc = grpc.ServiceDesc{
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "internal/machine/api/pb/cluster.proto",
|
||||
Metadata: "api/pb/cluster.proto",
|
||||
}
|
||||
File renamed without changes.
@@ -2,7 +2,7 @@
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2
|
||||
// protoc v5.27.3
|
||||
// source: internal/machine/api/pb/common.proto
|
||||
// source: api/pb/common.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -58,11 +58,11 @@ func (x LogEntry_StreamType) String() string {
|
||||
}
|
||||
|
||||
func (LogEntry_StreamType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_internal_machine_api_pb_common_proto_enumTypes[0].Descriptor()
|
||||
return file_api_pb_common_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (LogEntry_StreamType) Type() protoreflect.EnumType {
|
||||
return &file_internal_machine_api_pb_common_proto_enumTypes[0]
|
||||
return &file_api_pb_common_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
|
||||
@@ -71,7 +71,7 @@ func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
|
||||
|
||||
// Deprecated: Use LogEntry_StreamType.Descriptor instead.
|
||||
func (LogEntry_StreamType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{7, 0}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{7, 0}
|
||||
}
|
||||
|
||||
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
|
||||
@@ -96,7 +96,7 @@ type Metadata struct {
|
||||
func (x *Metadata) Reset() {
|
||||
*x = Metadata{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[0]
|
||||
mi := &file_api_pb_common_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func (x *Metadata) String() string {
|
||||
func (*Metadata) ProtoMessage() {}
|
||||
|
||||
func (x *Metadata) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[0]
|
||||
mi := &file_api_pb_common_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -122,7 +122,7 @@ func (x *Metadata) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
|
||||
func (*Metadata) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{0}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Metadata) GetMachineId() string {
|
||||
@@ -172,7 +172,7 @@ type Empty struct {
|
||||
func (x *Empty) Reset() {
|
||||
*x = Empty{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[1]
|
||||
mi := &file_api_pb_common_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -185,7 +185,7 @@ func (x *Empty) String() string {
|
||||
func (*Empty) ProtoMessage() {}
|
||||
|
||||
func (x *Empty) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[1]
|
||||
mi := &file_api_pb_common_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -198,7 +198,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
|
||||
func (*Empty) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{1}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Empty) GetMetadata() *Metadata {
|
||||
@@ -221,7 +221,7 @@ type EmptyResponse struct {
|
||||
func (x *EmptyResponse) Reset() {
|
||||
*x = EmptyResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[2]
|
||||
mi := &file_api_pb_common_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (x *EmptyResponse) String() string {
|
||||
func (*EmptyResponse) ProtoMessage() {}
|
||||
|
||||
func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[2]
|
||||
mi := &file_api_pb_common_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -247,7 +247,7 @@ func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead.
|
||||
func (*EmptyResponse) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{2}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *EmptyResponse) GetMessages() []*Empty {
|
||||
@@ -268,7 +268,7 @@ type IP struct {
|
||||
func (x *IP) Reset() {
|
||||
*x = IP{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[3]
|
||||
mi := &file_api_pb_common_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -281,7 +281,7 @@ func (x *IP) String() string {
|
||||
func (*IP) ProtoMessage() {}
|
||||
|
||||
func (x *IP) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[3]
|
||||
mi := &file_api_pb_common_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -294,7 +294,7 @@ func (x *IP) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use IP.ProtoReflect.Descriptor instead.
|
||||
func (*IP) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{3}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *IP) GetIp() []byte {
|
||||
@@ -316,7 +316,7 @@ type IPPort struct {
|
||||
func (x *IPPort) Reset() {
|
||||
*x = IPPort{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[4]
|
||||
mi := &file_api_pb_common_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -329,7 +329,7 @@ func (x *IPPort) String() string {
|
||||
func (*IPPort) ProtoMessage() {}
|
||||
|
||||
func (x *IPPort) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[4]
|
||||
mi := &file_api_pb_common_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -342,7 +342,7 @@ func (x *IPPort) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use IPPort.ProtoReflect.Descriptor instead.
|
||||
func (*IPPort) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{4}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *IPPort) GetIp() *IP {
|
||||
@@ -371,7 +371,7 @@ type IPPrefix struct {
|
||||
func (x *IPPrefix) Reset() {
|
||||
*x = IPPrefix{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[5]
|
||||
mi := &file_api_pb_common_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -384,7 +384,7 @@ func (x *IPPrefix) String() string {
|
||||
func (*IPPrefix) ProtoMessage() {}
|
||||
|
||||
func (x *IPPrefix) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[5]
|
||||
mi := &file_api_pb_common_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -397,7 +397,7 @@ func (x *IPPrefix) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use IPPrefix.ProtoReflect.Descriptor instead.
|
||||
func (*IPPrefix) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{5}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *IPPrefix) GetIp() *IP {
|
||||
@@ -430,7 +430,7 @@ type LogsRequest struct {
|
||||
func (x *LogsRequest) Reset() {
|
||||
*x = LogsRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[6]
|
||||
mi := &file_api_pb_common_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -443,7 +443,7 @@ func (x *LogsRequest) String() string {
|
||||
func (*LogsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *LogsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[6]
|
||||
mi := &file_api_pb_common_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -456,7 +456,7 @@ func (x *LogsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use LogsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*LogsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{6}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *LogsRequest) GetId() string {
|
||||
@@ -508,7 +508,7 @@ type LogEntry struct {
|
||||
func (x *LogEntry) Reset() {
|
||||
*x = LogEntry{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[7]
|
||||
mi := &file_api_pb_common_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -521,7 +521,7 @@ func (x *LogEntry) String() string {
|
||||
func (*LogEntry) ProtoMessage() {}
|
||||
|
||||
func (x *LogEntry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_machine_api_pb_common_proto_msgTypes[7]
|
||||
mi := &file_api_pb_common_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -534,7 +534,7 @@ func (x *LogEntry) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
|
||||
func (*LogEntry) Descriptor() ([]byte, []int) {
|
||||
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{7}
|
||||
return file_api_pb_common_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *LogEntry) GetStream() LogEntry_StreamType {
|
||||
@@ -558,84 +558,82 @@ func (x *LogEntry) GetMessage() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_internal_machine_api_pb_common_proto protoreflect.FileDescriptor
|
||||
var File_api_pb_common_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_machine_api_pb_common_proto_rawDesc = []byte{
|
||||
0x0a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||
0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49,
|
||||
0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f,
|
||||
0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68,
|
||||
0x69, 0x6e, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a,
|
||||
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a, 0x05, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64,
|
||||
0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x37, 0x0a,
|
||||
0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26,
|
||||
0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
||||
0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x08, 0x6d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50, 0x12, 0x0e, 0x0a, 0x02,
|
||||
0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x22, 0x35, 0x0a, 0x06,
|
||||
0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12,
|
||||
0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x22, 0x75, 0x0a, 0x0b,
|
||||
0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66,
|
||||
0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x6c,
|
||||
0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x05, 0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6e,
|
||||
0x74, 0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||
0x12, 0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e,
|
||||
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65,
|
||||
0x61, 0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
|
||||
0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
|
||||
0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41,
|
||||
0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x03, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b,
|
||||
0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||
0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70,
|
||||
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
var file_api_pb_common_proto_rawDesc = []byte{
|
||||
0x0a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||
0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
|
||||
0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x61,
|
||||
0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||
0x6e, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||
0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x37, 0x0a, 0x0d,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a,
|
||||
0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x22, 0x35, 0x0a, 0x06, 0x49,
|
||||
0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f,
|
||||
0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x17,
|
||||
0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x22, 0x75, 0x0a, 0x0b, 0x4c,
|
||||
0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f,
|
||||
0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x6c,
|
||||
0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6e, 0x74,
|
||||
0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
|
||||
0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61,
|
||||
0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x6d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65,
|
||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
|
||||
0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06,
|
||||
0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x41, 0x52,
|
||||
0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x03, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69,
|
||||
0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_machine_api_pb_common_proto_rawDescOnce sync.Once
|
||||
file_internal_machine_api_pb_common_proto_rawDescData = file_internal_machine_api_pb_common_proto_rawDesc
|
||||
file_api_pb_common_proto_rawDescOnce sync.Once
|
||||
file_api_pb_common_proto_rawDescData = file_api_pb_common_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_machine_api_pb_common_proto_rawDescGZIP() []byte {
|
||||
file_internal_machine_api_pb_common_proto_rawDescOnce.Do(func() {
|
||||
file_internal_machine_api_pb_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_machine_api_pb_common_proto_rawDescData)
|
||||
func file_api_pb_common_proto_rawDescGZIP() []byte {
|
||||
file_api_pb_common_proto_rawDescOnce.Do(func() {
|
||||
file_api_pb_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_pb_common_proto_rawDescData)
|
||||
})
|
||||
return file_internal_machine_api_pb_common_proto_rawDescData
|
||||
return file_api_pb_common_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_machine_api_pb_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_internal_machine_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_internal_machine_api_pb_common_proto_goTypes = []any{
|
||||
var file_api_pb_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_api_pb_common_proto_goTypes = []any{
|
||||
(LogEntry_StreamType)(0), // 0: api.LogEntry.StreamType
|
||||
(*Metadata)(nil), // 1: api.Metadata
|
||||
(*Empty)(nil), // 2: api.Empty
|
||||
@@ -648,7 +646,7 @@ var file_internal_machine_api_pb_common_proto_goTypes = []any{
|
||||
(*status.Status)(nil), // 9: google.rpc.Status
|
||||
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
||||
}
|
||||
var file_internal_machine_api_pb_common_proto_depIdxs = []int32{
|
||||
var file_api_pb_common_proto_depIdxs = []int32{
|
||||
9, // 0: api.Metadata.status:type_name -> google.rpc.Status
|
||||
1, // 1: api.Empty.metadata:type_name -> api.Metadata
|
||||
2, // 2: api.EmptyResponse.messages:type_name -> api.Empty
|
||||
@@ -663,13 +661,13 @@ var file_internal_machine_api_pb_common_proto_depIdxs = []int32{
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_machine_api_pb_common_proto_init() }
|
||||
func file_internal_machine_api_pb_common_proto_init() {
|
||||
if File_internal_machine_api_pb_common_proto != nil {
|
||||
func init() { file_api_pb_common_proto_init() }
|
||||
func file_api_pb_common_proto_init() {
|
||||
if File_api_pb_common_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Metadata); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -681,7 +679,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*Empty); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -693,7 +691,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*EmptyResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -705,7 +703,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*IP); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -717,7 +715,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*IPPort); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -729,7 +727,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*IPPrefix); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -741,7 +739,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*LogsRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -753,7 +751,7 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_machine_api_pb_common_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||
file_api_pb_common_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||
switch v := v.(*LogEntry); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -770,19 +768,19 @@ func file_internal_machine_api_pb_common_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_machine_api_pb_common_proto_rawDesc,
|
||||
RawDescriptor: file_api_pb_common_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_internal_machine_api_pb_common_proto_goTypes,
|
||||
DependencyIndexes: file_internal_machine_api_pb_common_proto_depIdxs,
|
||||
EnumInfos: file_internal_machine_api_pb_common_proto_enumTypes,
|
||||
MessageInfos: file_internal_machine_api_pb_common_proto_msgTypes,
|
||||
GoTypes: file_api_pb_common_proto_goTypes,
|
||||
DependencyIndexes: file_api_pb_common_proto_depIdxs,
|
||||
EnumInfos: file_api_pb_common_proto_enumTypes,
|
||||
MessageInfos: file_api_pb_common_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_machine_api_pb_common_proto = out.File
|
||||
file_internal_machine_api_pb_common_proto_rawDesc = nil
|
||||
file_internal_machine_api_pb_common_proto_goTypes = nil
|
||||
file_internal_machine_api_pb_common_proto_depIdxs = nil
|
||||
File_api_pb_common_proto = out.File
|
||||
file_api_pb_common_proto_rawDesc = nil
|
||||
file_api_pb_common_proto_goTypes = nil
|
||||
file_api_pb_common_proto_depIdxs = nil
|
||||
}
|
||||
@@ -2,9 +2,9 @@ syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
||||
|
||||
// Vendored at internal/machine/api/vendor/google/rpc/status.proto.
|
||||
// Vendored at api/pb/vendor/google/rpc/status.proto.
|
||||
import "google/rpc/status.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,10 +2,10 @@ syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "internal/machine/api/pb/common.proto";
|
||||
import "api/pb/common.proto";
|
||||
|
||||
service Docker {
|
||||
rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse);
|
||||
@@ -2,7 +2,7 @@
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.27.3
|
||||
// source: internal/machine/api/pb/docker.proto
|
||||
// source: api/pb/docker.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -807,5 +807,5 @@ var Docker_ServiceDesc = grpc.ServiceDesc{
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "internal/machine/api/pb/docker.proto",
|
||||
Metadata: "api/pb/docker.proto",
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,12 +2,12 @@ syntax = "proto3";
|
||||
|
||||
package api;
|
||||
|
||||
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
||||
option go_package = "github.com/psviderski/uncloud/api/pb";
|
||||
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "internal/machine/api/pb/common.proto";
|
||||
import "api/pb/common.proto";
|
||||
|
||||
service Machine {
|
||||
// CheckPrerequisites verifies if the machine meets all necessary system requirements to participate in the cluster.
|
||||
@@ -19,6 +19,22 @@ service Machine {
|
||||
rpc Inspect(google.protobuf.Empty) returns (MachineInfo);
|
||||
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
|
||||
rpc InspectMachine(google.protobuf.Empty) returns (InspectMachineResponse);
|
||||
// WaitForStoreVersion waits until the cluster store on this machine has reached each requested actor version, with
|
||||
// no known missing or pending transactions through those versions from active members.
|
||||
// Corrosion may satisfy a version by applying its surviving changes or by marking it complete because its changes
|
||||
// have been superseded.
|
||||
//
|
||||
// Waiting normally makes the captured data available on this machine. However, another write may replace some of that
|
||||
// data before it arrives. Corrosion can then complete the older version without transferring the replaced data.
|
||||
// If the replacement is outside the requested versions, this RPC can succeed while the affected data is still missing
|
||||
// or outdated. This can happen during concurrent updates even when all machines are well connected.
|
||||
//
|
||||
// Success does not guarantee an exact snapshot or delivery of every historical value. Callers that require a specific
|
||||
// record or condition should verify it after waiting.
|
||||
//
|
||||
// This RPC observes native replication without initiating synchronisation.
|
||||
// Use the RPC deadline to bound the wait.
|
||||
rpc WaitForStoreVersion(WaitForStoreVersionRequest) returns (google.protobuf.Empty);
|
||||
// UpdateMachine updates the configuration of the machine.
|
||||
rpc UpdateMachine(UpdateMachineRequest) returns (UpdateMachineResponse);
|
||||
// InspectWireGuardNetwork retrieves the current WireGuard network configuration and peer status.
|
||||
@@ -105,8 +121,8 @@ message JoinClusterRequest {
|
||||
// MTU of the WireGuard interface on this machine. The daemon auto-detects the optimal MTU if 0 or not set.
|
||||
int32 wireguard_mtu = 7;
|
||||
// Cluster store version this machine must reach before participating.
|
||||
// Per-actor vector (Corrosion actor UUID → max applied db_version).
|
||||
map<string, int64> min_store_version = 6;
|
||||
// Per-actor vector (Corrosion actor UUID → minimum required db_version).
|
||||
map<string, uint64> min_store_version = 6;
|
||||
}
|
||||
|
||||
message InspectMachineResponse {
|
||||
@@ -119,9 +135,24 @@ message MachineDetails {
|
||||
MachineInfo machine = 2;
|
||||
// Round-trip times to other machines in the cluster, keyed by peer machine ID.
|
||||
map<string, RTTStats> rtts = 4;
|
||||
// Current cluster store version observed on this machine.
|
||||
// Per-actor vector (Corrosion actor UUID → max applied db_version) read from Corrosion crsql_db_versions.
|
||||
map<string, int64> store_version = 5;
|
||||
// Store replication progress observed on this machine.
|
||||
// Maps each Corrosion actor UUID to its highest processed database version. Corrosion also counts versions whose
|
||||
// changes were superseded and skipped. Versions at or below a reported version may still be missing or pending
|
||||
// locally.
|
||||
//
|
||||
// Pass this vector to WaitForStoreVersion on another machine to wait for replication through these versions.
|
||||
// See that RPC's data-availability limitations. To combine observations from several machines, take the maximum
|
||||
// for each actor.
|
||||
// Capturing this vector does not wait for replication or prevent further writes.
|
||||
map<string, uint64> store_version = 5;
|
||||
}
|
||||
|
||||
message WaitForStoreVersionRequest {
|
||||
// Minimum database version to reach for each Corrosion actor UUID.
|
||||
// Obtain a vector from InspectMachine. To combine several responses, take the maximum version for each actor.
|
||||
// Actors omitted from this vector and versions above its targets are not awaited. An empty vector requires
|
||||
// no replication. Unknown actors with positive targets remain pending until replication reaches them or the RPC ends.
|
||||
map<string, uint64> min_version = 1;
|
||||
}
|
||||
|
||||
message TokenResponse {
|
||||
@@ -2,7 +2,7 @@
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.27.3
|
||||
// source: internal/machine/api/pb/machine.proto
|
||||
// source: api/pb/machine.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
Machine_Token_FullMethodName = "/api.Machine/Token"
|
||||
Machine_Inspect_FullMethodName = "/api.Machine/Inspect"
|
||||
Machine_InspectMachine_FullMethodName = "/api.Machine/InspectMachine"
|
||||
Machine_WaitForStoreVersion_FullMethodName = "/api.Machine/WaitForStoreVersion"
|
||||
Machine_UpdateMachine_FullMethodName = "/api.Machine/UpdateMachine"
|
||||
Machine_InspectWireGuardNetwork_FullMethodName = "/api.Machine/InspectWireGuardNetwork"
|
||||
Machine_Reset_FullMethodName = "/api.Machine/Reset"
|
||||
@@ -46,6 +47,22 @@ type MachineClient interface {
|
||||
Inspect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MachineInfo, error)
|
||||
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
|
||||
InspectMachine(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*InspectMachineResponse, error)
|
||||
// WaitForStoreVersion waits until the cluster store on this machine has reached each requested actor version, with
|
||||
// no known missing or pending transactions through those versions from active members.
|
||||
// Corrosion may satisfy a version by applying its surviving changes or by marking it complete because its changes
|
||||
// have been superseded.
|
||||
//
|
||||
// Waiting normally makes the captured data available on this machine. However, another write may replace some of that
|
||||
// data before it arrives. Corrosion can then complete the older version without transferring the replaced data.
|
||||
// If the replacement is outside the requested versions, this RPC can succeed while the affected data is still missing
|
||||
// or outdated. This can happen during concurrent updates even when all machines are well connected.
|
||||
//
|
||||
// Success does not guarantee an exact snapshot or delivery of every historical value. Callers that require a specific
|
||||
// record or condition should verify it after waiting.
|
||||
//
|
||||
// This RPC observes native replication without initiating synchronisation.
|
||||
// Use the RPC deadline to bound the wait.
|
||||
WaitForStoreVersion(ctx context.Context, in *WaitForStoreVersionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
// UpdateMachine updates the configuration of the machine.
|
||||
UpdateMachine(ctx context.Context, in *UpdateMachineRequest, opts ...grpc.CallOption) (*UpdateMachineResponse, error)
|
||||
// InspectWireGuardNetwork retrieves the current WireGuard network configuration and peer status.
|
||||
@@ -124,6 +141,16 @@ func (c *machineClient) InspectMachine(ctx context.Context, in *emptypb.Empty, o
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *machineClient) WaitForStoreVersion(ctx context.Context, in *WaitForStoreVersionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, Machine_WaitForStoreVersion_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *machineClient) UpdateMachine(ctx context.Context, in *UpdateMachineRequest, opts ...grpc.CallOption) (*UpdateMachineResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(UpdateMachineResponse)
|
||||
@@ -196,6 +223,22 @@ type MachineServer interface {
|
||||
Inspect(context.Context, *emptypb.Empty) (*MachineInfo, error)
|
||||
// InspectMachine retrieves detailed information about the machine. Supports broadcasting to multiple machines.
|
||||
InspectMachine(context.Context, *emptypb.Empty) (*InspectMachineResponse, error)
|
||||
// WaitForStoreVersion waits until the cluster store on this machine has reached each requested actor version, with
|
||||
// no known missing or pending transactions through those versions from active members.
|
||||
// Corrosion may satisfy a version by applying its surviving changes or by marking it complete because its changes
|
||||
// have been superseded.
|
||||
//
|
||||
// Waiting normally makes the captured data available on this machine. However, another write may replace some of that
|
||||
// data before it arrives. Corrosion can then complete the older version without transferring the replaced data.
|
||||
// If the replacement is outside the requested versions, this RPC can succeed while the affected data is still missing
|
||||
// or outdated. This can happen during concurrent updates even when all machines are well connected.
|
||||
//
|
||||
// Success does not guarantee an exact snapshot or delivery of every historical value. Callers that require a specific
|
||||
// record or condition should verify it after waiting.
|
||||
//
|
||||
// This RPC observes native replication without initiating synchronisation.
|
||||
// Use the RPC deadline to bound the wait.
|
||||
WaitForStoreVersion(context.Context, *WaitForStoreVersionRequest) (*emptypb.Empty, error)
|
||||
// UpdateMachine updates the configuration of the machine.
|
||||
UpdateMachine(context.Context, *UpdateMachineRequest) (*UpdateMachineResponse, error)
|
||||
// InspectWireGuardNetwork retrieves the current WireGuard network configuration and peer status.
|
||||
@@ -232,6 +275,9 @@ func (UnimplementedMachineServer) Inspect(context.Context, *emptypb.Empty) (*Mac
|
||||
func (UnimplementedMachineServer) InspectMachine(context.Context, *emptypb.Empty) (*InspectMachineResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method InspectMachine not implemented")
|
||||
}
|
||||
func (UnimplementedMachineServer) WaitForStoreVersion(context.Context, *WaitForStoreVersionRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method WaitForStoreVersion not implemented")
|
||||
}
|
||||
func (UnimplementedMachineServer) UpdateMachine(context.Context, *UpdateMachineRequest) (*UpdateMachineResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateMachine not implemented")
|
||||
}
|
||||
@@ -376,6 +422,24 @@ func _Machine_InspectMachine_Handler(srv interface{}, ctx context.Context, dec f
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Machine_WaitForStoreVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(WaitForStoreVersionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MachineServer).WaitForStoreVersion(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Machine_WaitForStoreVersion_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MachineServer).WaitForStoreVersion(ctx, req.(*WaitForStoreVersionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Machine_UpdateMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateMachineRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -490,6 +554,10 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "InspectMachine",
|
||||
Handler: _Machine_InspectMachine_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "WaitForStoreVersion",
|
||||
Handler: _Machine_WaitForStoreVersion_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateMachine",
|
||||
Handler: _Machine_UpdateMachine_Handler,
|
||||
@@ -514,5 +582,5 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "internal/machine/api/pb/machine.proto",
|
||||
Metadata: "api/pb/machine.proto",
|
||||
}
|
||||
Vendored
File renamed without changes.
@@ -0,0 +1,167 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/psviderski/uncloud/pkg/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
machine string
|
||||
output string
|
||||
}
|
||||
|
||||
func NewListCommand() *cobra.Command {
|
||||
opts := listOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "ls",
|
||||
Aliases: []string{"list"},
|
||||
Short: "List certificates in cluster storage for Caddy.",
|
||||
Long: `List certificates stored in the Uncloud cluster storage for Caddy.
|
||||
|
||||
Caddy must use the Uncloud storage module (https://github.com/unlabs-dev/caddy-uncloud)
|
||||
configured with 'storage uncloud' in the global options for its certificates to appear here.
|
||||
|
||||
This inventory does not check whether Caddy currently serves or trusts a certificate.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||
return list(cmd.Context(), uncli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||
"Name or ID of the machine to read certificate storage from. (default is connected machine)")
|
||||
cmd.Flags().StringVarP(&opts.output, "output", "o", "",
|
||||
"Output format: 'json' or empty for a human-readable table.")
|
||||
completion.MachinesFlag(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
|
||||
if opts.output != "" && opts.output != "json" {
|
||||
return fmt.Errorf("unsupported output format '%s' (supported: json)", opts.output)
|
||||
}
|
||||
|
||||
clusterClient, err := uncli.ConnectCluster(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to cluster: %w", err)
|
||||
}
|
||||
defer clusterClient.Close()
|
||||
|
||||
certs, listErr := clusterClient.Caddy.ListCertificates(ctx,
|
||||
client.CaddyListCertificatesOptions{Machine: opts.machine})
|
||||
if len(certs) > 0 || listErr == nil {
|
||||
if err = printCertificates(os.Stdout, certs, opts.output); err != nil {
|
||||
return fmt.Errorf("print Caddy certificates: %w", err)
|
||||
}
|
||||
}
|
||||
if listErr != nil {
|
||||
if len(certs) > 0 {
|
||||
return fmt.Errorf("certificate list is partial: %w", listErr)
|
||||
}
|
||||
return fmt.Errorf("list Caddy certificates: %w", listErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printCertificates(out io.Writer, certs []api.IssuedCertificate, output string) error {
|
||||
type certificate struct {
|
||||
api.IssuedCertificate
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
items := make([]certificate, 0, len(certs))
|
||||
for _, cert := range certs {
|
||||
fingerprint := cert.Fingerprint()
|
||||
items = append(items, certificate{
|
||||
IssuedCertificate: cert,
|
||||
Fingerprint: hex.EncodeToString(fingerprint[:]),
|
||||
})
|
||||
}
|
||||
slices.SortFunc(items, func(a, b certificate) int {
|
||||
if cmp := strings.Compare(a.SAN, b.SAN); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
if cmp := a.Chain[0].NotAfter.Compare(b.Chain[0].NotAfter); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return strings.Compare(a.Fingerprint, b.Fingerprint)
|
||||
})
|
||||
|
||||
if output == "json" {
|
||||
encoder := json.NewEncoder(out)
|
||||
encoder.SetIndent("", " ")
|
||||
if err := encoder.Encode(items); err != nil {
|
||||
return fmt.Errorf("marshal certificates: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
_, err := fmt.Fprintln(out, "No Caddy certificates found in cluster storage.")
|
||||
return err
|
||||
}
|
||||
|
||||
t := tui.NewTable()
|
||||
t.Headers("ID", "NAME", "ISSUER", "EXPIRES")
|
||||
for _, item := range items {
|
||||
t.Row(
|
||||
item.Fingerprint[:12],
|
||||
item.SAN,
|
||||
formatIssuer(item.IssuerData),
|
||||
formatExpiry(item.Chain[0].NotAfter),
|
||||
)
|
||||
}
|
||||
_, err := lipgloss.Fprintln(out, t)
|
||||
return err
|
||||
}
|
||||
|
||||
func formatIssuer(data api.CertificateIssuerData) string {
|
||||
if data.ACME == nil {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
switch data.ACME.CA {
|
||||
case certmagic.LetsEncryptProductionCA:
|
||||
return "Let's Encrypt"
|
||||
case certmagic.LetsEncryptStagingCA:
|
||||
return "Let's Encrypt (staging)"
|
||||
case certmagic.ZeroSSLProductionCA:
|
||||
return "ZeroSSL"
|
||||
case certmagic.GoogleTrustProductionCA:
|
||||
return "Google Trust Services"
|
||||
case certmagic.GoogleTrustStagingCA:
|
||||
return "Google Trust Services (staging)"
|
||||
default:
|
||||
return data.ACME.CA
|
||||
}
|
||||
}
|
||||
|
||||
func formatExpiry(expires time.Time) string {
|
||||
delta := expires.Sub(time.Now())
|
||||
if delta <= 0 {
|
||||
return fmt.Sprintf("%s (expired %s ago)", expires.UTC().Format(time.DateOnly),
|
||||
strings.ToLower(units.HumanDuration(-delta)))
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", expires.UTC().Format(time.DateOnly),
|
||||
strings.ToLower(units.HumanDuration(delta)))
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrintCertificates(t *testing.T) {
|
||||
now := time.Now().UTC().Truncate(time.Hour)
|
||||
certs := []api.IssuedCertificate{
|
||||
testIssuedCertificate("z.example.com", "z", now.Add(48*time.Hour), certmagic.LetsEncryptProductionCA),
|
||||
testIssuedCertificate("a.example.com", "old", now.Add(-48*time.Hour), certmagic.LetsEncryptStagingCA),
|
||||
testIssuedCertificate("a.example.com", "new", now.Add(72*time.Hour), "https://ca.example/directory"),
|
||||
testIssuedCertificate("unknown.example.com", "unknown", now.Add(96*time.Hour), ""),
|
||||
}
|
||||
|
||||
var table bytes.Buffer
|
||||
require.NoError(t, printCertificates(&table, certs, ""))
|
||||
output := table.String()
|
||||
assert.Contains(t, output, "ID")
|
||||
assert.Contains(t, output, "NAME")
|
||||
assert.Contains(t, output, "ISSUER")
|
||||
assert.Contains(t, output, "EXPIRES")
|
||||
assert.Contains(t, output, now.Add(-48*time.Hour).Format(time.DateOnly)+" (expired ")
|
||||
assert.Contains(t, output, now.Add(48*time.Hour).Format(time.DateOnly)+" (")
|
||||
assert.Contains(t, output, "Let's Encrypt (staging)")
|
||||
assert.Contains(t, output, "https://ca.example/directory")
|
||||
assert.Less(t, strings.Index(output, now.Add(-48*time.Hour).Format(time.DateOnly)),
|
||||
strings.Index(output, now.Add(72*time.Hour).Format(time.DateOnly)))
|
||||
assert.Less(t, strings.Index(output, "a.example.com"), strings.Index(output, "z.example.com"))
|
||||
assert.Contains(t, output, testFingerprint("old")[:12])
|
||||
|
||||
var encoded bytes.Buffer
|
||||
require.NoError(t, printCertificates(&encoded, certs, "json"))
|
||||
var items []struct {
|
||||
api.IssuedCertificate
|
||||
Fingerprint string
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(encoded.Bytes(), &items))
|
||||
require.Len(t, items, 4)
|
||||
assert.Equal(t, testFingerprint("old"), items[0].Fingerprint)
|
||||
assert.Equal(t, "a.example.com", items[0].SAN)
|
||||
require.Len(t, items[0].Chain, 1)
|
||||
assert.Equal(t, []byte("old"), items[0].Chain[0].Raw)
|
||||
assert.Equal(t, now.Add(-48*time.Hour), items[0].Chain[0].NotAfter)
|
||||
require.NotNil(t, items[0].IssuerData.ACME)
|
||||
assert.Equal(t, certmagic.LetsEncryptStagingCA, items[0].IssuerData.ACME.CA)
|
||||
assert.Equal(t, testFingerprint("new"), items[1].Fingerprint)
|
||||
require.NotNil(t, items[1].IssuerData.ACME)
|
||||
assert.Equal(t, "https://ca.example/directory", items[1].IssuerData.ACME.CA)
|
||||
assert.Nil(t, items[2].IssuerData.ACME)
|
||||
assert.Equal(t, "z.example.com", items[3].SAN)
|
||||
}
|
||||
|
||||
func TestPrintCertificates_Empty(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
output string
|
||||
want string
|
||||
}{
|
||||
{output: "", want: "No Caddy certificates found in cluster storage.\n"},
|
||||
{output: "json", want: "[]\n"},
|
||||
} {
|
||||
var out bytes.Buffer
|
||||
require.NoError(t, printCertificates(&out, nil, tt.output))
|
||||
assert.Equal(t, tt.want, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatIssuer(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
ca string
|
||||
want string
|
||||
}{
|
||||
{name: "production", ca: certmagic.LetsEncryptProductionCA, want: "Let's Encrypt"},
|
||||
{name: "staging", ca: certmagic.LetsEncryptStagingCA, want: "Let's Encrypt (staging)"},
|
||||
{name: "ZeroSSL", ca: certmagic.ZeroSSLProductionCA, want: "ZeroSSL"},
|
||||
{name: "Google Trust Services", ca: certmagic.GoogleTrustProductionCA, want: "Google Trust Services"},
|
||||
{name: "Google Trust Services staging", ca: certmagic.GoogleTrustStagingCA, want: "Google Trust Services (staging)"},
|
||||
{name: "other", ca: "https://ca.example/directory", want: "https://ca.example/directory"},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, formatIssuer(api.CertificateIssuerData{
|
||||
ACME: &api.ACMEIssuerData{CA: tt.ca},
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
assert.Equal(t, "unknown", formatIssuer(api.CertificateIssuerData{}))
|
||||
}
|
||||
|
||||
func TestFormatExpiry_UTC(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
brisbane := time.FixedZone("AEST", 10*60*60)
|
||||
expires := time.Date(now.Year(), now.Month(), now.Day()+3, 3, 0, 0, 0, brisbane)
|
||||
assert.True(t, strings.HasPrefix(formatExpiry(expires), expires.UTC().Format(time.DateOnly)+" ("))
|
||||
assert.NotEqual(t, expires.Format(time.DateOnly), expires.UTC().Format(time.DateOnly))
|
||||
expired := now.Add(-2 * time.Hour)
|
||||
assert.True(t, strings.HasPrefix(formatExpiry(expired), expired.UTC().Format(time.DateOnly)+" (expired "))
|
||||
assert.Contains(t, formatExpiry(expired), " ago)")
|
||||
}
|
||||
|
||||
func testIssuedCertificate(name, raw string, expires time.Time, ca string) api.IssuedCertificate {
|
||||
cert := api.IssuedCertificate{
|
||||
SAN: name,
|
||||
Chain: []*x509.Certificate{{Raw: []byte(raw), NotAfter: expires}},
|
||||
}
|
||||
if ca != "" {
|
||||
cert.IssuerData.ACME = &api.ACMEIssuerData{CA: ca}
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
func testFingerprint(raw string) string {
|
||||
sum := sha256.Sum256([]byte(raw))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cert
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
func NewRootCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cert",
|
||||
Short: "Inspect certificates in cluster storage for Caddy.",
|
||||
}
|
||||
cmd.AddCommand(NewListCommand())
|
||||
return cmd
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/alecthomas/chroma/v2/quick"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/pkg/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -46,12 +48,7 @@ func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
|
||||
}
|
||||
defer clusterClient.Close()
|
||||
|
||||
if opts.machine != "" {
|
||||
// If a specific machine is requested, use it to get the Caddy configuration.
|
||||
ctx = clusterClient.ProxySingleMachineContext(ctx, opts.machine)
|
||||
}
|
||||
|
||||
config, err := clusterClient.Caddy.GetConfig(ctx, nil)
|
||||
config, err := clusterClient.Caddy.Config(ctx, client.CaddyConfigOptions{Machine: opts.machine})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get Caddy config: %w", err)
|
||||
}
|
||||
@@ -66,5 +63,10 @@ func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
if config.LastReconciliationError != "" {
|
||||
tui.PrintWarning(fmt.Sprintf("last Caddy config load failed: %s\nShowing the last saved Caddyfile.",
|
||||
config.LastReconciliationError))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/psviderski/uncloud/pkg/client"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -110,7 +110,9 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
||||
placement := api.Placement{
|
||||
Machines: cli.ExpandCommaSeparatedValues(opts.machines),
|
||||
}
|
||||
d, err := clusterClient.NewCaddyDeployment(opts.image, caddyfile, placement)
|
||||
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{
|
||||
Image: opts.image, Config: caddyfile, Placement: placement,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create caddy deployment: %w", err)
|
||||
}
|
||||
@@ -202,6 +204,10 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
||||
}
|
||||
return fmt.Errorf("get cluster domain: %w", err)
|
||||
}
|
||||
if !domain.Reserved {
|
||||
fmt.Printf("Skipping DNS records update as cluster domain '%s' is managed externally.\n", domain.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Updating cluster domain records in Uncloud DNS to point to machines running caddy service...")
|
||||
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
||||
@@ -216,7 +222,7 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
||||
if errors.Is(err, client.ErrNoReachableMachines) {
|
||||
fmt.Println()
|
||||
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
||||
"machines running caddy containers.\n", domain)
|
||||
"machines running caddy containers.\n", domain.Name)
|
||||
fmt.Println()
|
||||
fmt.Println("Possible solutions:")
|
||||
fmt.Println("- Ensure your machines have public IP addresses")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"github.com/psviderski/uncloud/cmd/uc/caddy/cert"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -10,6 +11,7 @@ func NewRootCommand() *cobra.Command {
|
||||
Short: "Manage Caddy reverse proxy service.",
|
||||
}
|
||||
cmd.AddCommand(
|
||||
cert.NewRootCommand(),
|
||||
NewConfigCommand(),
|
||||
NewDeployCommand(),
|
||||
NewLogsCommand(),
|
||||
|
||||
@@ -37,6 +37,7 @@ func release(ctx context.Context, uncli *cli.CLI) error {
|
||||
if status.Convert(err).Code() == codes.NotFound {
|
||||
return errors.New("no domain reserved")
|
||||
}
|
||||
return fmt.Errorf("release cluster domain: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Released cluster domain: %s\n", domain.Name)
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/psviderski/uncloud/pkg/client"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -49,7 +49,8 @@ func reserve(ctx context.Context, uncli *cli.CLI, opts reserveOptions) error {
|
||||
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.endpoint})
|
||||
if err != nil {
|
||||
if status.Convert(err).Code() == codes.AlreadyExists {
|
||||
return errors.New("domain already reserved")
|
||||
return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' " +
|
||||
"or release a reservation with 'uc dns release' first")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
+6
-3
@@ -7,16 +7,19 @@ import (
|
||||
func NewRootCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "dns",
|
||||
Short: "Manage cluster domain in Uncloud DNS.",
|
||||
Long: "Manage cluster domain in Uncloud DNS.\n" +
|
||||
Short: "Manage the cluster domain.",
|
||||
Long: "Manage the cluster domain.\n" +
|
||||
"DNS commands allow you to reserve or release a unique 'xxxxxx.uncld.dev' domain for your " +
|
||||
"cluster. When reserved, Caddy service deployments will automatically update DNS records to route " +
|
||||
"traffic to the services in the cluster.",
|
||||
"traffic to the services in the cluster.\n\n" +
|
||||
"EXPERIMENTAL: Use 'uc dns set' to configure an externally managed domain instead. " +
|
||||
"Uncloud does not manage DNS records for manually set domains.",
|
||||
}
|
||||
cmd.AddCommand(
|
||||
NewReleaseCommand(),
|
||||
NewReserveCommand(),
|
||||
NewShowCommand(),
|
||||
NewSetCommand(),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/internal/grpcversion"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func NewSetCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "set DOMAIN_NAME",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Set or unset an externally managed cluster domain (EXPERIMENTAL).",
|
||||
Long: "EXPERIMENTAL: Set the cluster domain used to generate ingress hostnames for services.\n" +
|
||||
"Configure wildcard DNS records for this domain with your DNS provider. " +
|
||||
"Uncloud will not create, verify, update, or delete external DNS records.\n" +
|
||||
"Pass an empty string to unset a manually set domain. " +
|
||||
"Use 'uc dns release' to release a domain reserved in Uncloud DNS. " +
|
||||
"Setting or unsetting the domain does not change existing service hostnames.",
|
||||
Example: " uc dns set apps.example.com\n uc dns set \"\"",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||
return set(cmd.Context(), uncli, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func set(ctx context.Context, uncli *cli.CLI, name string) error {
|
||||
if name != "" {
|
||||
if labels, ok := dns.IsDomainName(name); !ok || labels < 2 {
|
||||
return fmt.Errorf("invalid cluster domain %q: must be a valid domain name with at least two labels", name)
|
||||
}
|
||||
}
|
||||
tui.PrintWarning("Setting an externally managed cluster domain is experimental.")
|
||||
|
||||
clusterClient, err := uncli.ConnectCluster(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to cluster: %w", err)
|
||||
}
|
||||
defer clusterClient.Close()
|
||||
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, grpcversion.MetadataKeyMinServerVersion, "0.21.0")
|
||||
_, err = clusterClient.SetDomain(ctx, &pb.SetDomainRequest{Name: name})
|
||||
if err != nil {
|
||||
if status.Convert(err).Code() == codes.AlreadyExists {
|
||||
return errors.New("cluster domain already configured, unset it with 'uc dns set \"\"' or " +
|
||||
"release a reservation with 'uc dns release' first")
|
||||
}
|
||||
return fmt.Errorf("set cluster domain: %w", err)
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
fmt.Println("Unset cluster domain. External DNS records have not been changed.")
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Set cluster domain: %s\n", name)
|
||||
fmt.Println("DNS is managed externally. " +
|
||||
"Configure wildcard DNS records pointing to machines running the Caddy service.")
|
||||
return nil
|
||||
}
|
||||
+2
-2
@@ -33,11 +33,11 @@ func show(ctx context.Context, uncli *cli.CLI) error {
|
||||
domain, err := clusterClient.GetDomain(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, api.ErrNotFound) {
|
||||
return errors.New("no domain reserved")
|
||||
return errors.New("no cluster domain configured")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(domain)
|
||||
fmt.Println(domain.Name)
|
||||
return nil
|
||||
}
|
||||
+12
-6
@@ -40,13 +40,19 @@ func NewAddCommand() *cobra.Command {
|
||||
Short: "Add a remote machine to a cluster.",
|
||||
Long: `Add a new machine to an existing Uncloud cluster.
|
||||
|
||||
By default, it installs Docker and the Uncloud daemon on the machine over SSH unless --no-install
|
||||
is specified, which assumes they are already installed and running.
|
||||
|
||||
Connection methods:
|
||||
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||
|
||||
cli.BindEnvToFlag(cmd, "version", "UNCLOUD_DAEMON_VERSION")
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||
|
||||
// Determine connection mode and strip scheme.
|
||||
@@ -79,7 +85,7 @@ Connection methods:
|
||||
)
|
||||
cmd.Flags().BoolVar(
|
||||
&opts.noInstall, "no-install", false,
|
||||
"Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+
|
||||
"Skip installation of Docker and the Uncloud daemon on the machine. "+
|
||||
"Assumes they're already installed and running.",
|
||||
)
|
||||
cmd.Flags().StringVar(
|
||||
@@ -94,7 +100,7 @@ Connection methods:
|
||||
)
|
||||
cmd.Flags().StringVar(
|
||||
&opts.version, "version", "latest",
|
||||
"Version of the Uncloud daemon to install on the machine.",
|
||||
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
|
||||
)
|
||||
cmd.Flags().StringSliceVar(
|
||||
&opts.wgEndpoints, "wg-endpoint", nil,
|
||||
@@ -186,7 +192,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
||||
// Deploy a Caddy service container to the added machine. If caddy service is already deployed on other machines,
|
||||
// use the deployed image version.
|
||||
// NOTE: We use the cluster client to inspect and scale the Caddy service because the newly added machine may have
|
||||
// issues accessing the Machine API of existing machines in the cluster.
|
||||
// issues accessing the API of existing machines in the cluster.
|
||||
// See the issue for more details: https://github.com/psviderski/uncloud/issues/65.
|
||||
caddyImage := ""
|
||||
caddySvc, err := clusterClient.InspectService(ctx, client.CaddyServiceName)
|
||||
@@ -213,7 +219,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Preparing Caddy deployment...")
|
||||
d, err := clusterClient.NewCaddyDeployment(caddyImage, "", api.Placement{})
|
||||
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Image: caddyImage})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create caddy deployment: %w", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package machine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// runVersionFlagEnvTest builds the given command for each case, stubs out RunE so only flag
|
||||
// parsing and PreRunE run (no SSH calls), and asserts the resolved --version flag value.
|
||||
// Note: cannot use t.Parallel() because subtests use t.Setenv().
|
||||
func runVersionFlagEnvTest(t *testing.T, newCommand func() *cobra.Command) {
|
||||
t.Helper()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
env string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "env var sets version when flag not passed",
|
||||
args: []string{"root@localhost"},
|
||||
env: "v1.2.3",
|
||||
want: "v1.2.3",
|
||||
},
|
||||
{
|
||||
name: "explicit flag wins over env var",
|
||||
args: []string{"root@localhost", "--version", "v9.9.9"},
|
||||
env: "v1.2.3",
|
||||
want: "v9.9.9",
|
||||
},
|
||||
{
|
||||
name: "defaults to latest when neither flag nor env var set",
|
||||
args: []string{"root@localhost"},
|
||||
want: "latest",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.env != "" {
|
||||
t.Setenv("UNCLOUD_DAEMON_VERSION", tt.env)
|
||||
}
|
||||
|
||||
cmd := newCommand()
|
||||
cmd.RunE = func(*cobra.Command, []string) error {
|
||||
return nil
|
||||
}
|
||||
cmd.SetArgs(tt.args)
|
||||
require.NoError(t, cmd.Execute())
|
||||
|
||||
version, err := cmd.Flags().GetString("version")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, version)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitCommandVersionFlagEnvVar verifies that the UNCLOUD_DAEMON_VERSION environment variable
|
||||
// is bound to the --version flag of 'machine init', and that an explicit flag wins.
|
||||
func TestInitCommandVersionFlagEnvVar(t *testing.T) {
|
||||
runVersionFlagEnvTest(t, NewInitCommand)
|
||||
}
|
||||
|
||||
// TestAddCommandVersionFlagEnvVar verifies that the UNCLOUD_DAEMON_VERSION environment variable
|
||||
// is bound to the --version flag of 'machine add', and that an explicit flag wins.
|
||||
func TestAddCommandVersionFlagEnvVar(t *testing.T) {
|
||||
runVersionFlagEnvTest(t, NewAddCommand)
|
||||
}
|
||||
+18
-12
@@ -8,15 +8,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/cmd/uc/caddy"
|
||||
"github.com/psviderski/uncloud/cmd/uc/dns"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/config"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/cluster"
|
||||
"github.com/psviderski/uncloud/internal/machine/network"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/psviderski/uncloud/pkg/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -45,6 +45,9 @@ func NewInitCommand() *cobra.Command {
|
||||
Long: `Initialise a new cluster by setting up a remote machine as the first member.
|
||||
This command creates a new context in your Uncloud config to manage the cluster.
|
||||
|
||||
By default, it installs Docker and the Uncloud daemon on the machine over SSH unless --no-install
|
||||
is specified, which assumes they are already installed and running.
|
||||
|
||||
Connection methods:
|
||||
[ssh://]user@host - Use system 'ssh' command with full SSH config support (default, no prefix required)
|
||||
ssh+go://user@host - Use Go's built-in SSH library`,
|
||||
@@ -62,9 +65,12 @@ Connection methods:
|
||||
uc machine init root@<your-server-ip> --no-caddy --no-dns`,
|
||||
// TODO: support initialising a cluster on the local machine.
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||
|
||||
cli.BindEnvToFlag(cmd, "version", "UNCLOUD_DAEMON_VERSION")
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||
|
||||
var remoteMachine *cli.RemoteMachine
|
||||
@@ -117,7 +123,7 @@ Connection methods:
|
||||
)
|
||||
cmd.Flags().BoolVar(
|
||||
&opts.noInstall, "no-install", false,
|
||||
"Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+
|
||||
"Skip installation of Docker and the Uncloud daemon on the machine. "+
|
||||
"Assumes they're already installed and running.",
|
||||
)
|
||||
cmd.Flags().StringVar(
|
||||
@@ -132,7 +138,7 @@ Connection methods:
|
||||
)
|
||||
cmd.Flags().StringVar(
|
||||
&opts.version, "version", "latest",
|
||||
"Version of the Uncloud daemon to install on the machine.",
|
||||
"Version of the Uncloud daemon to install on the machine. [$UNCLOUD_DAEMON_VERSION]",
|
||||
)
|
||||
cmd.Flags().StringSliceVar(
|
||||
&opts.wgEndpoints, "wg-endpoint", nil,
|
||||
@@ -215,17 +221,17 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
||||
initOpts.WireguardEndpoints = endpoints
|
||||
}
|
||||
|
||||
client, err := uncli.InitCluster(ctx, initOpts)
|
||||
clusterClient, err := uncli.InitCluster(ctx, initOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
defer clusterClient.Close()
|
||||
|
||||
// Since the cluster API needs a few moments to become ready after cluster initialisation,
|
||||
// we keep the user informed during this wait. We wait here even if no Caddy or DNS is requested
|
||||
// as the cluster needs to be ready so that commands such as 'uc machine ls' work immediately after init.
|
||||
err = tui.RunSpinner(ctx, "Waiting for the cluster to be ready...", func(ctx context.Context) error {
|
||||
return client.WaitClusterReady(ctx, 1*time.Minute)
|
||||
return clusterClient.WaitClusterReady(ctx, 1*time.Minute)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("wait for cluster to be ready: %w", err)
|
||||
@@ -239,7 +245,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
||||
fmt.Println()
|
||||
|
||||
if !opts.noDNS {
|
||||
domain, err := client.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.dnsEndpoint})
|
||||
domain, err := clusterClient.ReserveDomain(ctx, &pb.ReserveDomainRequest{Endpoint: opts.dnsEndpoint})
|
||||
if err != nil {
|
||||
return fmt.Errorf("reserve cluster domain in Uncloud DNS: %w", err)
|
||||
}
|
||||
@@ -247,7 +253,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
||||
}
|
||||
|
||||
if !opts.noCaddy {
|
||||
d, err := client.NewCaddyDeployment("", "", api.Placement{})
|
||||
d, err := clusterClient.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create caddy deployment: %w", err)
|
||||
}
|
||||
@@ -263,7 +269,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
return caddy.UpdateDomainRecords(ctx, client, uncli.ProgressOut())
|
||||
return caddy.UpdateDomainRecords(ctx, clusterClient, uncli.ProgressOut())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
"charm.land/lipgloss/v2/tree"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -136,8 +136,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
||||
return fmt.Errorf("confirm removal: %w", err)
|
||||
}
|
||||
if !confirmed {
|
||||
fmt.Println("Cancelled. Machine was not removed.")
|
||||
return nil
|
||||
return cli.Cancelled("Cancelled. Machine was not removed.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/network"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
+35
-2
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/psviderski/uncloud/internal/machine"
|
||||
"github.com/psviderski/uncloud/internal/version"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type globalOptions struct {
|
||||
@@ -42,6 +43,13 @@ func main() {
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Shell completion runs through the hidden __complete command which has flag parsing disabled,
|
||||
// so the global flags from the completed command line are never parsed. Apply them manually to make
|
||||
// completion work with --connect, --context, and --uncloud-config.
|
||||
if cmd.Name() == cobra.ShellCompRequestCmd {
|
||||
applyGlobalFlagsFromCompletionArgs(cmd.Root().PersistentFlags(), os.Args[1:])
|
||||
}
|
||||
|
||||
cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT")
|
||||
cli.BindEnvToFlag(cmd, "context", "UNCLOUD_CONTEXT")
|
||||
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
|
||||
@@ -80,10 +88,11 @@ func main() {
|
||||
|
||||
configPath := fs.ExpandHomeDir(opts.configPath)
|
||||
|
||||
// Make uc connect via the local Unix socket when running on a cluster machine.
|
||||
if opts.connect == "" {
|
||||
if !fs.Exists(configPath) && fs.Exists(machine.DefaultUncloudSockPath) {
|
||||
if !fs.Exists(configPath) && fs.Exists(machine.DefaultClusterAPISockPath) {
|
||||
conn = &config.MachineConnection{
|
||||
Unix: machine.DefaultUncloudSockPath,
|
||||
Unix: machine.DefaultClusterAPISockPath,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,3 +168,27 @@ func main() {
|
||||
cobra.CheckErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// applyGlobalFlagsFromCompletionArgs parses the global flags from the raw arguments of a __complete command and applies
|
||||
// the ones found to flags. The trailing word being completed, unknown flags, and positional arguments are ignored.
|
||||
func applyGlobalFlagsFromCompletionArgs(flags *pflag.FlagSet, args []string) {
|
||||
// The shell always passes the word being completed as the last argument, even if it's empty.
|
||||
// Exclude it from parsing as its value may not be complete yet.
|
||||
if len(args) == 0 {
|
||||
return
|
||||
}
|
||||
args = args[:len(args)-1]
|
||||
|
||||
fset := pflag.NewFlagSet("global", pflag.ContinueOnError)
|
||||
fset.ParseErrorsAllowlist.UnknownFlags = true
|
||||
fset.String("connect", "", "")
|
||||
fset.StringP("context", "c", "", "")
|
||||
fset.String("uncloud-config", "", "")
|
||||
// Parsing an incomplete command line may fail, apply the flags parsed so far anyway.
|
||||
_ = fset.Parse(args)
|
||||
|
||||
fset.Visit(func(f *pflag.Flag) {
|
||||
// Setting the flag marks it as changed so it takes precedence over environment variables.
|
||||
_ = flags.Set(f.Name, f.Value.String())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestApplyGlobalFlagsFromCompletionArgs(t *testing.T) {
|
||||
defaultConfigPath := "~/.config/uncloud/config.yaml"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantConnect string
|
||||
wantContext string
|
||||
wantConfigPath string
|
||||
// Flag names expected to be marked as changed on the target flag set.
|
||||
wantChanged []string
|
||||
}{
|
||||
{
|
||||
name: "no flags",
|
||||
args: []string{"__complete", "inspect", ""},
|
||||
},
|
||||
{
|
||||
name: "connect with space",
|
||||
args: []string{"__complete", "--connect", "ssh://user@host", "inspect", ""},
|
||||
wantConnect: "ssh://user@host",
|
||||
wantChanged: []string{"connect"},
|
||||
},
|
||||
{
|
||||
name: "connect with equals",
|
||||
args: []string{"__complete", "--connect=tcp://127.0.0.1:51000", "inspect", ""},
|
||||
wantConnect: "tcp://127.0.0.1:51000",
|
||||
wantChanged: []string{"connect"},
|
||||
},
|
||||
{
|
||||
name: "context shorthand",
|
||||
args: []string{"__complete", "-c", "prod", "inspect", ""},
|
||||
wantContext: "prod",
|
||||
wantChanged: []string{"context"},
|
||||
},
|
||||
{
|
||||
name: "all flags",
|
||||
args: []string{"__complete", "--connect", "user@host", "-c", "prod", "--uncloud-config", "/tmp/uncloud.yaml", "inspect", ""},
|
||||
wantConnect: "user@host",
|
||||
wantContext: "prod",
|
||||
wantConfigPath: "/tmp/uncloud.yaml",
|
||||
wantChanged: []string{"connect", "context", "uncloud-config"},
|
||||
},
|
||||
{
|
||||
name: "unknown flags are ignored",
|
||||
args: []string{"__complete", "--quiet", "-n", "5", "--connect", "user@host", "logs", ""},
|
||||
wantConnect: "user@host",
|
||||
wantChanged: []string{"connect"},
|
||||
},
|
||||
{
|
||||
name: "flags after double dash are ignored",
|
||||
args: []string{"__complete", "exec", "svc", "--", "sh", "--connect", "user@host"},
|
||||
},
|
||||
{
|
||||
name: "flags before double dash are applied",
|
||||
args: []string{"__complete", "--connect", "user@host", "exec", "svc", "--", "sh", "-c", "env"},
|
||||
wantConnect: "user@host",
|
||||
wantChanged: []string{"connect"},
|
||||
},
|
||||
{
|
||||
name: "partial flag name being completed is excluded",
|
||||
args: []string{"__complete", "--connect", "user@host", "inspect", "--context"},
|
||||
wantConnect: "user@host",
|
||||
wantChanged: []string{"connect"},
|
||||
},
|
||||
{
|
||||
name: "partial flag value being completed is excluded",
|
||||
args: []string{"__complete", "--uncloud-config", "/tmp/"},
|
||||
},
|
||||
{
|
||||
name: "partial connect value being completed is excluded",
|
||||
args: []string{"__complete", "--connect", "tcp://127.0.0.1:5"},
|
||||
},
|
||||
{
|
||||
name: "completed flag value with partial command word",
|
||||
args: []string{"__complete", "--uncloud-config", "/tmp/uncloud.yaml", "insp"},
|
||||
wantConfigPath: "/tmp/uncloud.yaml",
|
||||
wantChanged: []string{"uncloud-config"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Mirror the global persistent flags defined on the root command.
|
||||
var opts globalOptions
|
||||
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
flags.StringVar(&opts.connect, "connect", "", "")
|
||||
flags.StringVarP(&opts.context, "context", "c", "", "")
|
||||
flags.StringVar(&opts.configPath, "uncloud-config", defaultConfigPath, "")
|
||||
|
||||
applyGlobalFlagsFromCompletionArgs(flags, tt.args)
|
||||
|
||||
assert.Equal(t, tt.wantConnect, opts.connect)
|
||||
assert.Equal(t, tt.wantContext, opts.context)
|
||||
wantConfigPath := tt.wantConfigPath
|
||||
if wantConfigPath == "" {
|
||||
wantConfigPath = defaultConfigPath
|
||||
}
|
||||
assert.Equal(t, wantConfigPath, opts.configPath)
|
||||
|
||||
for _, name := range []string{"connect", "context", "uncloud-config"} {
|
||||
assert.Equal(t, slices.Contains(tt.wantChanged, name), flags.Changed(name),
|
||||
"changed status of flag '%s'", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+15
-16
@@ -132,35 +132,34 @@ func runProxy(ctx context.Context, uncli *cli.CLI, opts proxyOptions) error {
|
||||
// endpoint and shuffles the data, *it* will actually experience errors.
|
||||
remoteAddr := net.JoinHostPort(ip.String(), strconv.Itoa(opts.remotePort))
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
p := &proxy.Proxy{
|
||||
Listener: listener,
|
||||
RemoteAddr: remoteAddr,
|
||||
DialContext: dialer.DialContext,
|
||||
OnError: func(err error) {
|
||||
fmt.Printf("Failed to proxy to '%s': %v\n", remoteAddr, err)
|
||||
cancel()
|
||||
if proxy.IsConnectionClosedError(err) {
|
||||
return
|
||||
}
|
||||
// A more actionable error instead of the cryptic [ssh -W] command error.
|
||||
if strings.Contains(err.Error(), "Session open refused by peer") {
|
||||
fmt.Printf("Could not connect to '%s': connection refused. "+
|
||||
"Check that the service is running and listening on port %d inside the container.\n",
|
||||
remoteAddr, opts.remotePort)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Failed to proxy a connection to '%s': %v\n", remoteAddr, err)
|
||||
},
|
||||
}
|
||||
|
||||
// Run the proxy in the background and signal when it has fully shut down.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
p.Run(ctx)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// Prefix the local address with the scheme for common HTTP ports so it becomes control-clickable in most
|
||||
// terminals. We assume plain HTTP since TLS is typically terminated by Caddy in front of the service.
|
||||
fmt.Printf("%s%s → %s (%s%s%s)\n", schemeForPort(opts.remotePort), p.Listener.Addr().String(),
|
||||
remoteAddr, opts.service, tui.Faint.Render("/"), containerID)
|
||||
|
||||
<-ctx.Done()
|
||||
// Wait for the proxy to drain in-flight connections and shut down gracefully.
|
||||
<-done
|
||||
|
||||
if err = p.Run(ctx); err != nil {
|
||||
return fmt.Errorf("run proxy to '%s': %w", remoteAddr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ func printContainers(containers []containerInfo) error {
|
||||
}
|
||||
|
||||
func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo, error) {
|
||||
listCtx := cli.ProxyMachinesContext(ctx, nil)
|
||||
listCtx := client.ProxyMachinesContext(ctx, nil)
|
||||
|
||||
// List all service containers across all machines in the cluster.
|
||||
machineContainers, err := cli.Docker.ListServiceContainers(
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
||||
"github.com/psviderski/uncloud/pkg/client"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -122,6 +122,7 @@ func NewRunCommand(groupID string) *cobra.Command {
|
||||
"Mount a data volume or host path into service containers. Service containers will be scheduled on the machine(s) where\n"+
|
||||
"the volume is located. Can be specified multiple times.\n"+
|
||||
"Format: volume_name:/container/path[:ro|volume-nocopy] or /host/path:/container/path[:ro]\n"+
|
||||
"Host and container paths support runtime templates such as {{.Container.Name}}.\n"+
|
||||
"Examples:\n"+
|
||||
" -v postgres-data:/var/lib/postgresql/data Mount volume 'postgres-data' to /var/lib/postgresql/data in container\n"+
|
||||
" -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+
|
||||
|
||||
@@ -30,9 +30,11 @@ func NewScaleCommand(groupID string) *cobra.Command {
|
||||
Short: "Scale a replicated service by changing the number of replicas.",
|
||||
Long: "Scale a replicated service by changing the number of replicas.",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
cli.BindEnvToFlag(cmd, "yes", "UNCLOUD_AUTO_CONFIRM")
|
||||
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||
|
||||
opts.service = args[0]
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
+1
-2
@@ -92,8 +92,7 @@ func remove(ctx context.Context, uncli *cli.CLI, names []string, opts removeOpti
|
||||
return fmt.Errorf("confirm removal: %w", err)
|
||||
}
|
||||
if !confirmed {
|
||||
fmt.Println("Cancelled. No volumes were removed.")
|
||||
return nil
|
||||
return cli.Cancelled("Cancelled. No volumes were removed.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli"
|
||||
"github.com/psviderski/uncloud/internal/cli/completion"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
@@ -23,7 +23,7 @@ func newDialStdioCommand() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultUncloudSockPath,
|
||||
cmd.Flags().StringVar(&socketPath, "socket", machine.DefaultClusterAPISockPath,
|
||||
"Path to the Uncloud API socket")
|
||||
|
||||
return cmd
|
||||
|
||||
+27
-22
@@ -1,6 +1,6 @@
|
||||
module github.com/psviderski/uncloud/experiment
|
||||
|
||||
go 1.26
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/dgraph-io/badger/v3 v3.2103.5
|
||||
@@ -18,17 +18,22 @@ require (
|
||||
github.com/psviderski/uncloud v0.0.0
|
||||
github.com/siderolabs/discovery-api v0.1.4
|
||||
github.com/siderolabs/discovery-client v0.1.9
|
||||
go.uber.org/zap v1.27.0
|
||||
go.uber.org/zap v1.28.0
|
||||
golang.org/x/net v0.59.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
google.golang.org/grpc v1.81.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/bgentry/speakeasy v0.2.0 // indirect
|
||||
github.com/caarlos0/go-version v0.2.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -74,18 +79,18 @@ require (
|
||||
github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 // indirect
|
||||
github.com/jbenet/goprocess v0.1.4 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/compress v1.19.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||
github.com/libp2p/go-libp2p v0.35.4 // indirect
|
||||
github.com/libp2p/go-libp2p-pubsub v0.11.0 // indirect
|
||||
github.com/libp2p/go-msgio v0.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/miekg/dns v1.1.65 // indirect
|
||||
github.com/miekg/dns v1.1.73 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/cli v1.1.5 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
@@ -108,32 +113,32 @@ require (
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/siderolabs/gen v0.4.8 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.57.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
golang.org/x/mod v0.41.0 // indirect
|
||||
golang.org/x/sync v0.23.0 // indirect
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/text v0.42.0 // indirect
|
||||
golang.org/x/time v0.16.0 // indirect
|
||||
golang.org/x/tools v0.50.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
|
||||
google.golang.org/grpc v1.74.2 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
gotest.tools/v3 v3.5.2 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
|
||||
+35
-4
@@ -5,6 +5,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
@@ -38,6 +40,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
|
||||
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/caarlos0/go-version v0.2.2 h1:5r+nlrg4H2wOVwWjqRqRRIRbZ7ytRmjC9xoMIP0a5kQ=
|
||||
github.com/caarlos0/go-version v0.2.2/go.mod h1:X+rI5VAtJDpcjCjeEIXpxGa5+rTcgur1FK66wS0/944=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -298,8 +302,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||
@@ -345,6 +351,7 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
@@ -364,6 +371,7 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
||||
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
|
||||
@@ -525,6 +533,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||
@@ -577,16 +586,20 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
|
||||
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
@@ -603,6 +616,7 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -618,6 +632,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
@@ -631,6 +646,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -650,6 +666,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -660,8 +677,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -694,8 +712,9 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
@@ -707,6 +726,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -721,10 +744,13 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
@@ -737,6 +763,7 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
@@ -745,6 +772,7 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -756,6 +784,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -776,6 +805,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package connector
|
||||
package wg
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -7,39 +7,48 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/cli/config"
|
||||
"github.com/psviderski/uncloud/internal/grpcversion"
|
||||
"github.com/psviderski/uncloud/internal/machine/constants"
|
||||
"github.com/psviderski/uncloud/internal/machine/network"
|
||||
"github.com/psviderski/uncloud/internal/machine/network/tunnel"
|
||||
"github.com/psviderski/uncloud/pkg/client"
|
||||
"github.com/psviderski/uncloud/internal/secret"
|
||||
"golang.org/x/net/proxy"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
// Machine describes a remote machine that the experimental connector can reach.
|
||||
type Machine struct {
|
||||
Host string
|
||||
PublicKey secret.Secret
|
||||
}
|
||||
|
||||
type User interface {
|
||||
ManagementIP() netip.Addr
|
||||
PrivateKey() secret.Secret
|
||||
}
|
||||
|
||||
// WireGuardConnector establishes a connection to the cluster API through a WireGuard tunnel
|
||||
// to one of the cluster machines.
|
||||
type WireGuardConnector struct {
|
||||
user *client.User
|
||||
machines []config.MachineConnection
|
||||
tun *tunnel.Tunnel
|
||||
user User
|
||||
machines []Machine
|
||||
tun *Tunnel
|
||||
}
|
||||
|
||||
func NewWireGuardConnector(user *client.User, machines []config.MachineConnection) *WireGuardConnector {
|
||||
func NewWireGuardConnector(user User, machines []Machine) *WireGuardConnector {
|
||||
return &WireGuardConnector{
|
||||
user: user,
|
||||
machines: machines,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle context cancelation.
|
||||
// TODO: handle context cancellation.
|
||||
func (c *WireGuardConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
|
||||
if len(c.machines) == 0 {
|
||||
return nil, fmt.Errorf("no machines to connect to")
|
||||
}
|
||||
// TODO: iterate over machines and try to connect to each one until successful.
|
||||
// For now, try to connect to only the first machine.
|
||||
// For now, try to connect to only the first machine.
|
||||
machine := c.machines[0]
|
||||
endpointIPs, err := net.LookupIP(machine.Host)
|
||||
if err != nil {
|
||||
@@ -49,25 +58,24 @@ func (c *WireGuardConnector) Connect(ctx context.Context) (*grpc.ClientConn, err
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse IP address %q: %w", endpointIPs[0].String(), err)
|
||||
}
|
||||
endpoint := netip.AddrPortFrom(endpointAddr, tunnel.DefaultEndpointPort)
|
||||
endpoint := netip.AddrPortFrom(endpointAddr, DefaultEndpointPort)
|
||||
machineManagementIP := network.ManagementIP(machine.PublicKey)
|
||||
machineAPIAddr := net.JoinHostPort(machineManagementIP.String(), strconv.Itoa(constants.MachineAPIPort))
|
||||
machineAPIAddr := net.JoinHostPort(machineManagementIP.String(), strconv.Itoa(constants.UncloudAPIPort))
|
||||
|
||||
tunCfg := &tunnel.Config{
|
||||
tunCfg := &Config{
|
||||
LocalAddress: c.user.ManagementIP(),
|
||||
LocalPrivateKey: c.user.PrivateKey(),
|
||||
RemotePublicKey: machine.PublicKey,
|
||||
RemoteNetwork: netip.PrefixFrom(machineManagementIP, 128),
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
if c.tun, err = tunnel.Connect(tunCfg); err != nil {
|
||||
if c.tun, err = Connect(tunCfg); err != nil {
|
||||
return nil, fmt.Errorf("establish WireGuard tunnel to %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(
|
||||
machineAPIAddr,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithDefaultServiceConfig(defaultServiceConfig),
|
||||
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
|
||||
return c.tun.DialContext(ctx, "tcp", addr)
|
||||
}),
|
||||
@@ -1,4 +1,4 @@
|
||||
package tunnel
|
||||
package wg
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
const (
|
||||
DefaultEndpointPort = 51820
|
||||
// DefaultKeepaliveInterval is sensible interval that works with a wide variety of firewalls.
|
||||
// DefaultKeepaliveInterval is a sensible interval that works with a wide variety of firewalls.
|
||||
DefaultKeepaliveInterval = 25 * time.Second
|
||||
)
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
module github.com/psviderski/uncloud
|
||||
|
||||
go 1.26
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
charm.land/bubbles/v2 v2.0.0
|
||||
charm.land/bubbletea/v2 v2.0.2
|
||||
charm.land/bubbletea/v2 v2.0.10
|
||||
charm.land/huh/v2 v2.0.1
|
||||
charm.land/lipgloss/v2 v2.0.1
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/squirrel v1.5.4
|
||||
github.com/alecthomas/chroma/v2 v2.20.0
|
||||
github.com/alecthomas/chroma/v2 v2.24.1
|
||||
github.com/caarlos0/go-version v0.2.2
|
||||
github.com/caddyserver/caddy/v2 v2.8.4
|
||||
github.com/caddyserver/caddy/v2 v2.11.4
|
||||
github.com/caddyserver/certmagic v0.25.4
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/charmbracelet/colorprofile v0.4.3
|
||||
github.com/charmbracelet/x/ansi v0.11.7
|
||||
@@ -33,78 +34,69 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/miekg/dns v1.1.65
|
||||
github.com/mholt/acmez/v3 v3.1.6
|
||||
github.com/miekg/dns v1.1.73
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/prometheus/client_golang v1.24.1
|
||||
github.com/psviderski/unregistry v0.4.1
|
||||
github.com/siderolabs/grpc-proxy v0.5.1
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/term v0.34.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
golang.org/x/crypto v0.57.0
|
||||
golang.org/x/net v0.59.0
|
||||
golang.org/x/sync v0.23.0
|
||||
golang.org/x/sys v0.48.0
|
||||
golang.org/x/term v0.46.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a
|
||||
google.golang.org/grpc v1.74.2
|
||||
google.golang.org/protobuf v1.36.9
|
||||
modernc.org/sqlite v1.36.3
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4
|
||||
google.golang.org/grpc v1.81.0
|
||||
google.golang.org/protobuf v1.36.12
|
||||
modernc.org/sqlite v1.49.1
|
||||
tags.cncf.io/container-device-interface v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/kms v1.19.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.13.0 // indirect
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
|
||||
github.com/aws/smithy-go v1.20.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect
|
||||
github.com/aws/smithy-go v1.25.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/buger/goterm v1.0.4 // indirect
|
||||
github.com/caddyserver/certmagic v0.21.4 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260525132238-948f4557a654 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect
|
||||
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.5 // indirect
|
||||
@@ -121,13 +113,9 @@ require (
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/distribution/v3 v3.0.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/docker/buildx v0.29.1 // indirect
|
||||
github.com/docker/cli-docs-tool v0.10.0 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
@@ -141,84 +129,59 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsevents v0.2.0 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||
github.com/go-kit/kit v0.13.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang/glog v1.2.5 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/cel-go v0.20.1 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
|
||||
github.com/in-toto/in-toto-golang v0.9.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect
|
||||
github.com/ishidawataru/sctp v0.0.0-20230406120618-7ff4192f6ff2 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.14.3 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgtype v1.14.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/compress v1.19.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/acmez/v2 v2.0.3 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.2 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/buildkit v0.25.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
@@ -239,45 +202,36 @@ require (
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.22.0 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
||||
github.com/opencontainers/selinux v1.12.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.48.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.3 // indirect
|
||||
github.com/prometheus/common v0.71.0 // indirect
|
||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.22.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.62.0 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect
|
||||
github.com/redis/go-redis/v9 v9.7.3 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/slackhq/nebula v1.6.1 // indirect
|
||||
github.com/smallstep/certificates v0.26.1 // indirect
|
||||
github.com/smallstep/nosql v0.6.1 // indirect
|
||||
github.com/smallstep/pkcs7 v0.1.1 // indirect
|
||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
|
||||
github.com/smallstep/truststore v0.13.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/viper v1.12.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||
@@ -285,7 +239,6 @@ require (
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
|
||||
github.com/urfave/cli v1.22.17 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
@@ -293,70 +246,58 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.8.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.step.sm/cli-utils v0.9.0 // indirect
|
||||
go.step.sm/crypto v0.45.0 // indirect
|
||||
go.step.sm/linkedca v0.20.1 // indirect
|
||||
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.2.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
|
||||
go.uber.org/zap v1.28.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/text v0.42.0 // indirect
|
||||
golang.org/x/time v0.16.0 // indirect
|
||||
golang.org/x/tools v0.50.0 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
k8s.io/api v0.32.3 // indirect
|
||||
k8s.io/apimachinery v0.32.3 // indirect
|
||||
k8s.io/client-go v0.32.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
modernc.org/libc v1.61.13 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.2 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
replace charm.land/bubbletea/v2 => github.com/unlabs-dev/bubbletea/v2 v2.0.8-uncloud.1
|
||||
+3
-3
@@ -8,9 +8,9 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli/config"
|
||||
"github.com/psviderski/uncloud/internal/machine"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/cluster"
|
||||
"github.com/psviderski/uncloud/internal/machine/network"
|
||||
"github.com/psviderski/uncloud/internal/sshexec"
|
||||
@@ -458,7 +458,7 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (_ *clie
|
||||
}
|
||||
|
||||
// Snapshot the cluster store version so the new machine can catch up before participating.
|
||||
var storeVersion map[string]int64
|
||||
var storeVersion api.StoreVersion
|
||||
inspectResp, err = c.MachineClient.InspectMachine(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
// TODO(lhf): remove Unimplemented check when v0.17.0 is released.
|
||||
@@ -603,7 +603,7 @@ func provisionOrConnectRemoteMachine(
|
||||
if remoteMachine.User != rootUser {
|
||||
// provisionMachine has just added the user to the uncloud group. Any SSH ControlMaster left over from
|
||||
// a previous uc invocation (e.g. a failed uc command against the uninitialised machine) still holds
|
||||
// the old user groups and would deny access to /run/uncloud/uncloud.sock. Close the current session
|
||||
// the old user groups and would deny access to /run/uncloud/api/uncloud.sock. Close the current session
|
||||
// if it exists so the next session picks up the new groups.
|
||||
conn.CloseControlMaster(ctx)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ import (
|
||||
)
|
||||
|
||||
func Contexts(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||
// There are no contexts to complete when the CLI uses a direct machine connection (--connect) without a config.
|
||||
if uncli.Config == nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
contexts := slices.Sorted(maps.Keys(uncli.Config.Contexts))
|
||||
|
||||
names := []cobra.Completion{}
|
||||
@@ -21,7 +26,6 @@ func Contexts(ctx context.Context, uncli *cli.CLI, args []string, toComplete str
|
||||
if strings.HasPrefix(context, toComplete) {
|
||||
names = append(names, context)
|
||||
}
|
||||
names = append(names, context)
|
||||
}
|
||||
|
||||
return names, cobra.ShellCompDirectiveNoFileComp
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func Machines(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||
client, err := uncli.ConnectCluster(ctx)
|
||||
// Disable the connection progress output to not interfere with the shell completion output.
|
||||
client, err := uncli.ConnectClusterWithOptions(ctx, cli.ConnectOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func Services(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||
client, err := uncli.ConnectCluster(ctx)
|
||||
// Disable the connection progress output to not interfere with the shell completion output.
|
||||
client, err := uncli.ConnectClusterWithOptions(ctx, cli.ConnectOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func Volumes(ctx context.Context, uncli *cli.CLI, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
|
||||
client, err := uncli.ConnectCluster(ctx)
|
||||
// Disable the connection progress output to not interfere with the shell completion output.
|
||||
client, err := uncli.ConnectClusterWithOptions(ctx, cli.ConnectOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/secret"
|
||||
)
|
||||
|
||||
type MachineConnection struct {
|
||||
@@ -23,10 +21,8 @@ type MachineConnection struct {
|
||||
// The pointer is used to omit the field when not set. Otherwise, yaml marshalling includes an empty object.
|
||||
TCP *netip.AddrPort `yaml:"tcp,omitempty"`
|
||||
// Unix is the path to the machine's API unix socket.
|
||||
Unix string `yaml:"unix,omitempty"`
|
||||
Host string `yaml:"host,omitempty"`
|
||||
PublicKey secret.Secret `yaml:"public_key,omitempty"`
|
||||
MachineID string `yaml:"machine_id,omitempty"`
|
||||
Unix string `yaml:"unix,omitempty"`
|
||||
MachineID string `yaml:"machine_id,omitempty"`
|
||||
}
|
||||
|
||||
func (c *MachineConnection) String() string {
|
||||
|
||||
@@ -70,9 +70,9 @@ func TestMachineConnection_String(t *testing.T) {
|
||||
{
|
||||
name: "unix connection",
|
||||
conn: MachineConnection{
|
||||
Unix: "/run/uncloud/uncloud.sock",
|
||||
Unix: "/run/uncloud/api/uncloud.sock",
|
||||
},
|
||||
want: "unix:///run/uncloud/uncloud.sock",
|
||||
want: "unix:///run/uncloud/api/uncloud.sock",
|
||||
},
|
||||
{
|
||||
name: "no connection",
|
||||
|
||||
+11
-8
@@ -45,7 +45,17 @@ func connectClusterWithProgress(ctx context.Context, conn config.MachineConnecti
|
||||
}
|
||||
|
||||
// Run the connection TUI model. Render to stderr so stdout stays clean for command output.
|
||||
p := tea.NewProgram(newConnectModel(ctx, conn), tea.WithOutput(os.Stderr))
|
||||
p := tea.NewProgram(newConnectModel(ctx, conn),
|
||||
tea.WithOutput(os.Stderr),
|
||||
tea.WithInput(nil),
|
||||
// Ctrl+C handling
|
||||
tea.WithFilter(func(_ tea.Model, msg tea.Msg) tea.Msg {
|
||||
if _, ok := msg.(tea.InterruptMsg); ok {
|
||||
return connectResultMsg{err: errors.New("connection cancelled")}
|
||||
}
|
||||
return msg
|
||||
}),
|
||||
)
|
||||
model, err := p.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("run connection TUI: %w", err)
|
||||
@@ -190,13 +200,6 @@ func (m connectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m.spinner, cmd = m.spinner.Update(msg)
|
||||
return m, cmd
|
||||
|
||||
case tea.KeyPressMsg:
|
||||
if msg.String() == "ctrl+c" {
|
||||
m.result.err = fmt.Errorf("connection cancelled")
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
"charm.land/huh/v2"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/sshexec"
|
||||
"github.com/psviderski/uncloud/scripts"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
@@ -31,6 +31,8 @@ func RunSpinner(ctx context.Context, title string, action func(ctx context.Conte
|
||||
}
|
||||
})).
|
||||
WithOutput(os.Stderr).
|
||||
// A spinner reads no keys, saying so makes Bubble Tea skip unnecessary terminal capability queries.
|
||||
WithInput(nil).
|
||||
Context(ctx).
|
||||
ActionWithErr(action).
|
||||
Run()
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const adminCommandTimeout = 5 * time.Second
|
||||
|
||||
// AdminClient is a client for the Corrosion admin API.
|
||||
type AdminClient struct {
|
||||
sockPath string
|
||||
@@ -32,10 +34,15 @@ type Response struct {
|
||||
// The channel will be closed after sending the last or error response. The caller must read from the channel until
|
||||
// it is closed.
|
||||
func (c *AdminClient) SendCommand(cmd []byte) (<-chan Response, error) {
|
||||
conn, err := net.Dial("unix", c.sockPath)
|
||||
// TODO: Accept contexts in admin methods so callers can cancel requests and set their deadlines.
|
||||
conn, err := net.DialTimeout("unix", c.sockPath, adminCommandTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect to admin socket: %w", err)
|
||||
}
|
||||
if err = conn.SetDeadline(time.Now().Add(adminCommandTimeout)); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("set admin connection deadline: %w", err)
|
||||
}
|
||||
|
||||
if _, err = conn.Write(encodeFrame(cmd)); err != nil {
|
||||
conn.Close()
|
||||
|
||||
@@ -2,24 +2,50 @@ package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
|
||||
"github.com/coreos/go-systemd/activation"
|
||||
systemd "github.com/coreos/go-systemd/daemon"
|
||||
"github.com/psviderski/uncloud/internal/machine"
|
||||
)
|
||||
|
||||
const systemdSocketUnit = "uncloud.socket"
|
||||
|
||||
type Daemon struct {
|
||||
machine *machine.Machine
|
||||
}
|
||||
|
||||
func New(dataDir string) (*Daemon, error) {
|
||||
listeners, err := activation.ListenersWithNames()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get systemd-activated sockets: %w", err)
|
||||
}
|
||||
|
||||
listener, err := selectActivatedListener(listeners)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if listener != nil {
|
||||
slog.Info("Using systemd-activated API socket.", "addr", listener.Addr().String())
|
||||
}
|
||||
|
||||
config := &machine.Config{
|
||||
DataDir: dataDir,
|
||||
DataDir: dataDir,
|
||||
ClusterAPISockPath: machine.DefaultClusterAPISockPath,
|
||||
ClusterAPIListener: listener,
|
||||
}
|
||||
mach, err := machine.NewMachine(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init machine: %w", err)
|
||||
initErr := fmt.Errorf("init machine: %w", err)
|
||||
if listener != nil {
|
||||
if closeErr := listener.Close(); closeErr != nil {
|
||||
initErr = errors.Join(initErr, fmt.Errorf("close systemd-activated socket: %w", closeErr))
|
||||
}
|
||||
}
|
||||
return nil, initErr
|
||||
}
|
||||
|
||||
return &Daemon{
|
||||
@@ -27,6 +53,40 @@ func New(dataDir string) (*Daemon, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// selectActivatedListener returns the systemd activated listener. If systemd supplied multiple listeners,
|
||||
// it returns the one associated with systemdSocketUnit.
|
||||
func selectActivatedListener(listeners map[string][]net.Listener) (net.Listener, error) {
|
||||
var activated []net.Listener
|
||||
for _, named := range listeners {
|
||||
activated = append(activated, named...)
|
||||
}
|
||||
|
||||
if len(activated) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(activated) == 1 {
|
||||
return activated[0], nil
|
||||
}
|
||||
|
||||
named, ok := listeners[systemdSocketUnit]
|
||||
if !ok || len(named) != 1 {
|
||||
return nil, fmt.Errorf("expected one systemd-activated socket named '%s', received %d",
|
||||
systemdSocketUnit, len(named))
|
||||
}
|
||||
|
||||
// Close all other unused listeners.
|
||||
for name, unused := range listeners {
|
||||
if name == systemdSocketUnit {
|
||||
continue
|
||||
}
|
||||
for _, l := range unused {
|
||||
_ = l.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return named[0], nil
|
||||
}
|
||||
|
||||
func (d *Daemon) Run(ctx context.Context) error {
|
||||
slog.Info("Starting machine.")
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSelectActivatedListener(t *testing.T) {
|
||||
t.Run("no activated listeners", func(t *testing.T) {
|
||||
listener, err := selectActivatedListener(nil)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, listener)
|
||||
})
|
||||
|
||||
t.Run("only listener regardless of name", func(t *testing.T) {
|
||||
activated := testListener(t)
|
||||
|
||||
listener, err := selectActivatedListener(map[string][]net.Listener{"another.socket": {activated}})
|
||||
require.NoError(t, err)
|
||||
assert.Same(t, activated, listener)
|
||||
})
|
||||
|
||||
t.Run("named listener when multiple", func(t *testing.T) {
|
||||
activated := testListener(t)
|
||||
extra := testListener(t)
|
||||
|
||||
listener, err := selectActivatedListener(map[string][]net.Listener{
|
||||
systemdSocketUnit: {activated},
|
||||
"another.socket": {extra},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Same(t, activated, listener)
|
||||
assert.Error(t, extra.Close())
|
||||
})
|
||||
|
||||
t.Run("multiple listeners without expected name", func(t *testing.T) {
|
||||
first := testListener(t)
|
||||
second := testListener(t)
|
||||
|
||||
listener, err := selectActivatedListener(map[string][]net.Listener{
|
||||
"first.socket": {first},
|
||||
"second.socket": {second},
|
||||
})
|
||||
require.ErrorContains(t, err, "expected one systemd-activated socket")
|
||||
assert.Nil(t, listener)
|
||||
})
|
||||
|
||||
t.Run("multiple listeners under expected name", func(t *testing.T) {
|
||||
first := testListener(t)
|
||||
second := testListener(t)
|
||||
|
||||
listener, err := selectActivatedListener(map[string][]net.Listener{
|
||||
systemdSocketUnit: {first, second},
|
||||
})
|
||||
require.ErrorContains(t, err, "expected one systemd-activated socket")
|
||||
assert.Nil(t, listener)
|
||||
})
|
||||
}
|
||||
|
||||
func testListener(t *testing.T) net.Listener {
|
||||
t.Helper()
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = listener.Close() })
|
||||
|
||||
return listener
|
||||
}
|
||||
@@ -31,9 +31,8 @@ const (
|
||||
//
|
||||
// The two minimums are independent: a client might require a newer daemon for new
|
||||
// features, while that same daemon could still handle requests from older clients.
|
||||
// TODO: update to 0.20.0 before releasing 0.20.0.
|
||||
MinClientVersion = "0.20.0-nightly"
|
||||
MinServerVersion = "0.20.0-nightly"
|
||||
MinClientVersion = "0.20.0"
|
||||
MinServerVersion = "0.20.0"
|
||||
|
||||
ReleaseURL = "https://github.com/psviderski/uncloud/releases/latest"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package proxy
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/siderolabs/grpc-proxy/proxy"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/encoding/protowire"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
)
|
||||
|
||||
// MachineTarget represents a resolved machine target.
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
@@ -57,14 +58,12 @@ https://{{$hostname}} {
|
||||
log
|
||||
}{{end}}
|
||||
`
|
||||
caddyfileUnavailabeFooter = `# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine
|
||||
# (not accessible via the shared admin socket /run/uncloud/caddy/admin.sock) or the latest
|
||||
# generated config is invalid. Please check the service 'caddy' is running (uc inspect caddy)
|
||||
# and its logs for more details (uc logs caddy).
|
||||
`
|
||||
bootstrapMarker = "# Uncloud bootstrap for Caddy container "
|
||||
legacyBootstrapMarker = "# NOTE: User-defined configs for services were skipped because Caddy is not running"
|
||||
)
|
||||
|
||||
// CaddyfileGenerator generates a Caddyfile configuration for the Caddy reverse proxy.
|
||||
// It combines generated routes from published ports with user-defined Caddyfile snippets from service specs (x-caddy).
|
||||
type CaddyfileGenerator struct {
|
||||
// machineID is the unique identifier of the machine where the controller is running.
|
||||
machineID string
|
||||
@@ -74,11 +73,18 @@ type CaddyfileGenerator struct {
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// CaddyfileValidator is an interface for validating Caddyfile configurations.
|
||||
// CaddyfileValidator checks a candidate Caddyfile without loading it. Validate returns InvalidCaddyfileError only
|
||||
// when the candidate is known to be invalid. Other errors mean the check could not be completed. A successful check
|
||||
// does not guarantee that the configuration will load.
|
||||
type CaddyfileValidator interface {
|
||||
Validate(ctx context.Context, caddyfile string) error
|
||||
}
|
||||
|
||||
// InvalidCaddyfileError means validation completed and the candidate Caddyfile was rejected.
|
||||
type InvalidCaddyfileError struct{ Message string }
|
||||
|
||||
func (e *InvalidCaddyfileError) Error() string { return e.Message }
|
||||
|
||||
func NewCaddyfileGenerator(
|
||||
machineID, machineName string, validator CaddyfileValidator, log *slog.Logger,
|
||||
) *CaddyfileGenerator {
|
||||
@@ -93,35 +99,41 @@ func NewCaddyfileGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Generate creates a Caddyfile for the local Caddy container from the supplied healthy application containers.
|
||||
// Application service ports provide the generated sites. The Caddy container's custom Caddy config (x-caddy), if
|
||||
// defined, provides the global block even when that container is unhealthy. When application custom Caddy configs
|
||||
// are included, the newest container for each service provides its config.
|
||||
//
|
||||
// The final Caddyfile structure includes:
|
||||
// The resulting Caddyfile has this structure:
|
||||
//
|
||||
// [caddy x-caddy (global config)]
|
||||
// [generated Caddyfile from all service ports]
|
||||
// [service-a x-caddy]
|
||||
// [caddy custom Caddy config (global)]
|
||||
// [generated sites from application service ports]
|
||||
// [service-a custom Caddy config]
|
||||
// ...
|
||||
// [service-z x-caddy]
|
||||
// [service-z custom Caddy config]
|
||||
//
|
||||
// If includeCustom is false, custom Caddy configs (x-caddy) are not included in the generated Caddyfile.
|
||||
// Bootstrap mode keeps the global custom Caddy config and generated sites, but omits application custom Caddy configs.
|
||||
// It skips validation because Caddy may not be running yet. In this case, Caddy validates the config when it starts.
|
||||
// Global template errors still stop generation. In full mode, invalid globals stop generation, while invalid
|
||||
// application custom Caddy configs are logged and skipped.
|
||||
func (g *CaddyfileGenerator) Generate(
|
||||
ctx context.Context, records []store.ContainerRecord, includeCustom bool,
|
||||
ctx context.Context,
|
||||
caddyCtr api.ServiceContainer,
|
||||
records []store.ContainerRecord,
|
||||
bootstrap bool,
|
||||
) (string, error) {
|
||||
records = slices.Clone(records)
|
||||
// Sort records by local machine first, then by service name and creation time. Placing containers on the local
|
||||
// machine first lets user-defined Caddy configs pair this ordering with the "first" lb_policy to always send
|
||||
// traffic to the same-host replica (skipping the cross-machine hop) and only fall back to remote upstreams when
|
||||
// the local one is unhealthy.
|
||||
// The service name and creation time tiebreakers keep the generated Caddyfile stable across regenerations.
|
||||
// The service name, creation time, and container ID tiebreakers keep the file stable across regenerations.
|
||||
slices.SortStableFunc(records, func(a, b store.ContainerRecord) int {
|
||||
return cmp.Or(
|
||||
g.localMachineRank(a.MachineID)-g.localMachineRank(b.MachineID),
|
||||
strings.Compare(a.Container.ServiceName(), b.Container.ServiceName()),
|
||||
a.Container.CreatedTime().Compare(b.Container.CreatedTime()),
|
||||
strings.Compare(a.Container.ID, b.Container.ID),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -136,26 +148,11 @@ func (g *CaddyfileGenerator) Generate(
|
||||
}
|
||||
|
||||
caddyfileHeader := fmt.Sprintf(caddyfileHeaderFmt, g.machineName, time.Now().UTC().Format(time.RFC3339))
|
||||
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() != "" {
|
||||
if caddyCtr.ServiceSpec.CaddyConfig() != "" {
|
||||
// Render the custom global Caddy config as a Go template with the upstreams.
|
||||
tmplCtx := templateContext{
|
||||
Name: caddyCtr.ServiceName(),
|
||||
@@ -163,23 +160,23 @@ func (g *CaddyfileGenerator) Generate(
|
||||
}
|
||||
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)
|
||||
|
||||
return "", fmt.Errorf(
|
||||
"render template directives in user-defined global Caddy config from Caddy container %s: %w",
|
||||
caddyCtr.ShortID(), err)
|
||||
}
|
||||
caddyfileCandidate := fmt.Sprintf("# User-defined global config from service '%s'.\n%s\n\n%s",
|
||||
caddyCtr.ServiceName(), renderedConfig, caddyfile)
|
||||
if !bootstrap && g.validator != nil {
|
||||
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
|
||||
return "", fmt.Errorf("validate user-defined global Caddy config from Caddy container %s: %w",
|
||||
caddyCtr.ShortID(), err)
|
||||
}
|
||||
}
|
||||
caddyfile = caddyfileCandidate
|
||||
}
|
||||
|
||||
if bootstrap {
|
||||
return caddyfileHeader + bootstrapMarker + caddyCtr.ID + "\n\n" + caddyfile, nil
|
||||
}
|
||||
|
||||
// There could be multiple service containers for the same service with different custom Caddy configs, for example,
|
||||
@@ -200,7 +197,7 @@ func (g *CaddyfileGenerator) Generate(
|
||||
// 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.
|
||||
// Skip the caddy container as we already processed it as the global config.
|
||||
if serviceName == CaddyServiceName {
|
||||
continue
|
||||
}
|
||||
@@ -226,16 +223,22 @@ func (g *CaddyfileGenerator) Generate(
|
||||
|
||||
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
|
||||
if g.validator != nil {
|
||||
if err = g.validator.Validate(ctx, caddyfileCandidate); err != nil {
|
||||
if _, ok := errors.AsType[*InvalidCaddyfileError](err); !ok {
|
||||
return "", fmt.Errorf("validate Caddy config for service '%s': %w", serviceName, err)
|
||||
}
|
||||
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))
|
||||
continue
|
||||
}
|
||||
}
|
||||
caddyfile = caddyfileCandidate
|
||||
}
|
||||
|
||||
// Append error summary as comment if there were any invalid configs.
|
||||
// Keep skipped-snippet errors in the saved file so an operator can see why routes are missing after a restart.
|
||||
if len(configErrors) > 0 {
|
||||
var errorsComment strings.Builder
|
||||
errorsComment.WriteString("# Skipped invalid user-defined configs:\n")
|
||||
@@ -249,8 +252,7 @@ func (g *CaddyfileGenerator) Generate(
|
||||
return caddyfileHeader + "\n" + caddyfile, nil
|
||||
}
|
||||
|
||||
// localMachineRank returns 0 if the given machineID matches the local machine and 1 otherwise.
|
||||
// Useful for sorting containers running locally first.
|
||||
// localMachineRank is a sorting function for containers that puts running on the local machine first.
|
||||
func (g *CaddyfileGenerator) localMachineRank(machineID string) int {
|
||||
if g.machineID == machineID {
|
||||
return 0
|
||||
|
||||
@@ -208,10 +208,11 @@ http://app.example.com {
|
||||
ctx := context.Background()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Validator is not expected to be called in these tests.
|
||||
generator := NewCaddyfileGenerator("test-machine-id", "test-machine", nil, nil)
|
||||
validator := NewMockCaddyfileValidator(t)
|
||||
generator := NewCaddyfileGenerator("test-machine-id", "test-machine", validator, nil)
|
||||
|
||||
config, err := generator.Generate(ctx, tt.containers, true)
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.1", "", "", time.Now()).Container
|
||||
config, err := generator.Generate(ctx, caddyCtr, tt.containers, false)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
@@ -332,7 +333,7 @@ bad.template.com {
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "caddy service with invalid global config is skipped",
|
||||
name: "caddy service with invalid global config aborts",
|
||||
containers: []store.ContainerRecord{
|
||||
newContainerRecordWithCaddyConfig(
|
||||
"caddy",
|
||||
@@ -345,10 +346,7 @@ localhost {
|
||||
time.Now(),
|
||||
),
|
||||
},
|
||||
want: testCaddyfileHeader + `
|
||||
# Skipped invalid user-defined configs:
|
||||
# - service 'caddy': validation failed: invalid config detected
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "caddy service on different machine is ignored",
|
||||
@@ -786,7 +784,7 @@ web-v2.example.com {
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "multiple errors: invalid global, template error, and validation error",
|
||||
name: "invalid global aborts before application configs",
|
||||
containers: []store.ContainerRecord{
|
||||
newContainerRecordWithCaddyConfig(
|
||||
"caddy",
|
||||
@@ -827,17 +825,7 @@ invalid.example.com {
|
||||
time.Now(),
|
||||
),
|
||||
},
|
||||
want: testCaddyfileHeader + `
|
||||
# User-defined config for service 'valid'.
|
||||
valid.example.com {
|
||||
respond "Valid config"
|
||||
}
|
||||
|
||||
# Skipped invalid user-defined configs:
|
||||
# - service 'caddy': validation failed: invalid config detected
|
||||
# - service 'broken-template': failed to render template: parse config as Go template: template: Caddyfile:2: unterminated quoted string
|
||||
# - service 'invalid': validation failed: invalid config detected
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -846,7 +834,7 @@ valid.example.com {
|
||||
validator.EXPECT().Validate(mock.Anything, mock.Anything).RunAndReturn(
|
||||
func(ctx context.Context, caddyfile string) error {
|
||||
if strings.Contains(caddyfile, "# test:invalid") {
|
||||
return errors.New("invalid config detected")
|
||||
return &InvalidCaddyfileError{Message: "invalid config detected"}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -855,7 +843,8 @@ valid.example.com {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
generator := NewCaddyfileGenerator("test-machine-id", "test-machine", validator, nil)
|
||||
|
||||
config, err := generator.Generate(ctx, tt.containers, true)
|
||||
caddyCtr := caddyContainerFromTestContainers(tt.containers)
|
||||
config, err := generator.Generate(ctx, caddyCtr, tt.containers, false)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
@@ -868,6 +857,167 @@ valid.example.com {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaddyfileGeneratorBootstrap(t *testing.T) {
|
||||
// Bootstrap keeps the local Caddy container's globals and generated routes, but omits application custom configs.
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []store.ContainerRecord
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "global config retained and application custom configs skipped",
|
||||
containers: []store.ContainerRecord{
|
||||
newContainerRecordWithCaddyConfig(
|
||||
"caddy",
|
||||
"10.210.0.1",
|
||||
`# Global config
|
||||
{
|
||||
global directive
|
||||
}`,
|
||||
"test-machine-id",
|
||||
time.Now(),
|
||||
),
|
||||
newContainerRecordWithCaddyConfig(
|
||||
"web",
|
||||
"10.210.0.2",
|
||||
`web.example.com {
|
||||
reverse_proxy web:3000
|
||||
}`,
|
||||
"test-machine-id",
|
||||
time.Now(),
|
||||
),
|
||||
newContainerRecordWithPorts(
|
||||
"api",
|
||||
"10.210.0.3",
|
||||
[]string{"api.example.com:8080/http"},
|
||||
"test-machine-id",
|
||||
),
|
||||
},
|
||||
want: strings.Replace(testCaddyfileHeader, "\n\n# Health check endpoint", `
|
||||
# Uncloud bootstrap for Caddy container caddy-10.210.0.1
|
||||
|
||||
# User-defined global config from service 'caddy'.
|
||||
# Global config
|
||||
{
|
||||
global directive
|
||||
}
|
||||
|
||||
# Health check endpoint`, 1) + `
|
||||
# Sites generated from service ports.
|
||||
|
||||
http://api.example.com {
|
||||
reverse_proxy 10.210.0.3:8080 {
|
||||
import common_proxy
|
||||
}
|
||||
log
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no containers with x-caddy configs",
|
||||
containers: []store.ContainerRecord{
|
||||
newContainerRecordWithPorts(
|
||||
"api",
|
||||
"10.210.0.3",
|
||||
[]string{"api.example.com:8080/http"},
|
||||
"test-machine-id",
|
||||
),
|
||||
},
|
||||
want: strings.Replace(testCaddyfileHeader, "\n\n# Health check endpoint", `
|
||||
# Uncloud bootstrap for Caddy container caddy-10.210.0.1
|
||||
|
||||
# Health check endpoint`, 1) + `
|
||||
# Sites generated from service ports.
|
||||
|
||||
http://api.example.com {
|
||||
reverse_proxy 10.210.0.3:8080 {
|
||||
import common_proxy
|
||||
}
|
||||
log
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
validator := NewMockCaddyfileValidator(t)
|
||||
generator := NewCaddyfileGenerator("test-machine-id", "test-machine", validator, nil)
|
||||
|
||||
caddyContainer := caddyContainerFromTestContainers(tt.containers)
|
||||
config, err := generator.Generate(ctx, caddyContainer, tt.containers, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want, normaliseGeneratedTimestamp(config), "Generated Caddyfile doesn't match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaddyfileGenerator_ValidatorTransportErrorAbortsGeneration(t *testing.T) {
|
||||
validator := NewMockCaddyfileValidator(t)
|
||||
validator.EXPECT().Validate(mock.Anything, mock.Anything).Return(errors.New("socket disappeared"))
|
||||
|
||||
generator := NewCaddyfileGenerator("test-machine-id", "test-machine", validator, nil)
|
||||
app := newContainerRecordWithCaddyConfig(
|
||||
"web",
|
||||
"10.210.0.2",
|
||||
"web.example.com { respond ok }",
|
||||
"test-machine-id",
|
||||
time.Now(),
|
||||
)
|
||||
|
||||
_, err := generator.Generate(context.Background(), caddyContainerFromTestContainers(nil),
|
||||
[]store.ContainerRecord{app}, false)
|
||||
require.ErrorContains(t, err, "socket disappeared")
|
||||
}
|
||||
|
||||
func newContainer(ip string, ports ...string) api.ServiceContainer {
|
||||
portsLabel := strings.Join(ports, ",")
|
||||
return api.ServiceContainer{Container: api.Container{InspectResponse: container.InspectResponse{
|
||||
ContainerJSONBase: &container.ContainerJSONBase{
|
||||
State: &container.State{
|
||||
Running: true,
|
||||
},
|
||||
},
|
||||
NetworkSettings: &container.NetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
docker.NetworkName: {
|
||||
IPAddress: ip,
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
api.LabelServicePorts: portsLabel,
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}
|
||||
|
||||
func newContainerWithoutNetwork(ports ...string) api.ServiceContainer {
|
||||
portsLabel := strings.Join(ports, ",")
|
||||
return api.ServiceContainer{Container: api.Container{InspectResponse: container.InspectResponse{
|
||||
ContainerJSONBase: &container.ContainerJSONBase{
|
||||
State: &container.State{
|
||||
Running: true,
|
||||
},
|
||||
},
|
||||
NetworkSettings: &container.NetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"other-network": {
|
||||
IPAddress: "172.17.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
api.LabelServicePorts: portsLabel,
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}
|
||||
|
||||
func newContainerRecord(ctr api.ServiceContainer, machineID string) store.ContainerRecord {
|
||||
return store.ContainerRecord{
|
||||
Container: ctr,
|
||||
@@ -911,100 +1061,6 @@ func newContainerRecordWithCaddyConfig(serviceName, ip, caddyConfig, machineID s
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaddyfileGeneratorWithoutCustomConfigs(t *testing.T) {
|
||||
// Test that when includeCustom is false (Caddy not available), x-caddy configs are skipped.
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []store.ContainerRecord
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "x-caddy configs are skipped",
|
||||
containers: []store.ContainerRecord{
|
||||
newContainerRecordWithCaddyConfig(
|
||||
"caddy",
|
||||
"10.210.0.1",
|
||||
`# Global config
|
||||
{
|
||||
global directive
|
||||
}`,
|
||||
"test-machine-id",
|
||||
time.Now(),
|
||||
),
|
||||
newContainerRecordWithCaddyConfig(
|
||||
"web",
|
||||
"10.210.0.2",
|
||||
`web.example.com {
|
||||
reverse_proxy web:3000
|
||||
}`,
|
||||
"test-machine-id",
|
||||
time.Now(),
|
||||
),
|
||||
newContainerRecordWithPorts(
|
||||
"api",
|
||||
"10.210.0.3",
|
||||
[]string{"api.example.com:8080/http"},
|
||||
"test-machine-id",
|
||||
),
|
||||
},
|
||||
want: testCaddyfileHeader + `
|
||||
# Sites generated from service ports.
|
||||
|
||||
http://api.example.com {
|
||||
reverse_proxy 10.210.0.3:8080 {
|
||||
import common_proxy
|
||||
}
|
||||
log
|
||||
}
|
||||
|
||||
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine
|
||||
# (not accessible via the shared admin socket /run/uncloud/caddy/admin.sock) or the latest
|
||||
# generated config is invalid. Please check the service 'caddy' is running (uc inspect caddy)
|
||||
# and its logs for more details (uc logs caddy).
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no containers with x-caddy configs",
|
||||
containers: []store.ContainerRecord{
|
||||
newContainerRecordWithPorts(
|
||||
"api",
|
||||
"10.210.0.3",
|
||||
[]string{"api.example.com:8080/http"},
|
||||
"test-machine-id",
|
||||
),
|
||||
},
|
||||
want: testCaddyfileHeader + `
|
||||
# Sites generated from service ports.
|
||||
|
||||
http://api.example.com {
|
||||
reverse_proxy 10.210.0.3:8080 {
|
||||
import common_proxy
|
||||
}
|
||||
log
|
||||
}
|
||||
|
||||
# NOTE: User-defined configs for services were skipped because Caddy is not running on this machine
|
||||
# (not accessible via the shared admin socket /run/uncloud/caddy/admin.sock) or the latest
|
||||
# generated config is invalid. Please check the service 'caddy' is running (uc inspect caddy)
|
||||
# and its logs for more details (uc logs caddy).
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Validator is not expected to be called in these tests.
|
||||
generator := NewCaddyfileGenerator("test-machine-id", "test-machine", nil, nil)
|
||||
|
||||
config, err := generator.Generate(ctx, tt.containers, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want, normaliseGeneratedTimestamp(config), "Generated Caddyfile doesn't match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newContainerRecordWithPorts(serviceName, ip string, ports []string, machineID string) store.ContainerRecord {
|
||||
portsLabel := strings.Join(ports, ",")
|
||||
return store.ContainerRecord{
|
||||
@@ -1037,3 +1093,18 @@ func newContainerRecordWithPorts(serviceName, ip string, ports []string, machine
|
||||
MachineID: machineID,
|
||||
}
|
||||
}
|
||||
|
||||
// caddyContainerFromTestContainers returns the local Caddy container from the provided test containers,
|
||||
// or creates a default one if not found.
|
||||
func caddyContainerFromTestContainers(records []store.ContainerRecord) api.ServiceContainer {
|
||||
if caddyCtr := selectLocalCaddyContainer(records, "test-machine-id"); caddyCtr != nil {
|
||||
return *caddyCtr
|
||||
}
|
||||
return newContainerRecordWithCaddyConfig(
|
||||
"caddy",
|
||||
"10.210.0.1",
|
||||
"",
|
||||
"test-machine-id",
|
||||
time.Now(),
|
||||
).Container
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package caddyconfig
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -26,15 +25,15 @@ func NewCaddyAdminClient(socketPath string) *CaddyAdminClient {
|
||||
client: &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||
return net.Dial("unix", socketPath)
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return (&net.Dialer{}).DialContext(ctx, "unix", socketPath)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IsAvailable checks if the local Caddy instance is listening on the admin socket.
|
||||
// IsAvailable checks whether a Caddy process accepts connections at the admin socket.
|
||||
func (c *CaddyAdminClient) IsAvailable() bool {
|
||||
conn, err := net.DialTimeout("unix", c.socketPath, 1*time.Second)
|
||||
// A stale socket file left over from a crashed Caddy container returns ECONNREFUSED so this is correctly handled
|
||||
@@ -80,15 +79,16 @@ func (c *CaddyAdminClient) Adapt(ctx context.Context, caddyfile string) (string,
|
||||
// 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)
|
||||
if err = json.Unmarshal(body, &apiError); err == nil && apiError.Message != "" {
|
||||
return "", &InvalidCaddyfileError{Message: apiError.Message}
|
||||
}
|
||||
return "", &InvalidCaddyfileError{Message: string(body)}
|
||||
}
|
||||
|
||||
return "", errors.New(string(body))
|
||||
return "", fmt.Errorf("adapt request failed: HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Load loads a Caddyfile configuration into the Caddy instance running on the machine.
|
||||
// Load loads a Caddyfile configuration into the Caddy instance.
|
||||
// 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 {
|
||||
@@ -96,7 +96,11 @@ func (c *CaddyAdminClient) Load(ctx context.Context, caddyfile string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("adapt Caddyfile to JSON config: %w", err)
|
||||
}
|
||||
return c.LoadJSON(ctx, jsonConfig)
|
||||
}
|
||||
|
||||
// LoadJSON loads a JSON configuration into the Caddy instance.
|
||||
func (c *CaddyAdminClient) LoadJSON(ctx context.Context, jsonConfig string) error {
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "http://localhost/load", strings.NewReader(jsonConfig))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create load request: %w", err)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package caddyconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testAdminServer(t *testing.T, handler http.HandlerFunc) string {
|
||||
t.Helper()
|
||||
// macOS limits Unix socket paths to 104 bytes. t.TempDir can exceed that.
|
||||
dir, err := os.MkdirTemp("/tmp", "uc-caddy-")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = os.RemoveAll(dir)
|
||||
})
|
||||
|
||||
socketPath := filepath.Join(dir, "admin.sock")
|
||||
listener, err := net.Listen("unix", socketPath)
|
||||
require.NoError(t, err)
|
||||
server := httptest.NewUnstartedServer(handler)
|
||||
_ = server.Listener.Close()
|
||||
server.Listener = listener
|
||||
server.Start()
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
return socketPath
|
||||
}
|
||||
|
||||
func TestCaddyAdminClient_Validate(t *testing.T) {
|
||||
var status atomic.Int64
|
||||
status.Store(http.StatusBadRequest)
|
||||
socket := testAdminServer(t, func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(int(status.Load()))
|
||||
_, _ = w.Write([]byte(`{"message":"invalid config"}`))
|
||||
})
|
||||
client := NewCaddyAdminClient(socket)
|
||||
err := client.Validate(context.Background(), "invalid")
|
||||
var invalid *InvalidCaddyfileError
|
||||
assert.ErrorAs(t, err, &invalid)
|
||||
|
||||
status.Store(http.StatusInternalServerError)
|
||||
err = client.Validate(context.Background(), "invalid")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorAs(t, err, &invalid)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package caddyconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/fs"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
@@ -20,27 +21,40 @@ const (
|
||||
CaddyServiceName = "caddy"
|
||||
CaddyGroup = "uncloud"
|
||||
VerifyPath = "/.uncloud-verify"
|
||||
// periodicReconciliationInterval is the interval at which the controller reconciles the Caddyfile
|
||||
// even if no container changes are observed.
|
||||
periodicReconciliationInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
// 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.
|
||||
// Controller keeps the local Caddy reverse proxy in sync with container state from the cluster store. It writes a
|
||||
// Caddyfile that routes external traffic to healthy service containers across the internal network.
|
||||
//
|
||||
// Current Caddy deployments share one Caddyfile and one admin socket per machine. The controller must preserve a
|
||||
// saved full Caddyfile when Caddy is unavailable because that file may be needed to restart or roll back a container.
|
||||
// It writes a reduced bootstrap config only when no full file exists, including when an older offline file must be
|
||||
// replaced. The shared layout cannot give overlapping Caddy revisions separate configurations.
|
||||
type Controller struct {
|
||||
machineID string
|
||||
caddyfilePath string
|
||||
service *Service
|
||||
generator *CaddyfileGenerator
|
||||
client *CaddyAdminClient
|
||||
store *store.Store
|
||||
log *slog.Logger
|
||||
// lastFingerprint caches the fingerprint of the containers used to generate the latest successfully loaded
|
||||
// Caddyfile. nil means it hasn't been loaded yet or the last load failed.
|
||||
// Tests use their own group so they can exercise real file publication without the daemon's uncloud group.
|
||||
fileGroup string
|
||||
// lastFingerprint records the healthy application-container inputs used for the last full Caddyfile that was
|
||||
// successfully loaded into Caddy and saved to disk. It excludes the Caddy container, whose identity, start time,
|
||||
// and global config are tracked separately below.
|
||||
lastFingerprint []containerFingerprint
|
||||
// lastCaddyfile caches the last generated Caddyfile.
|
||||
lastCaddyfile string
|
||||
lastCaddyCtrID string
|
||||
lastStartedAt string
|
||||
lastGlobal string
|
||||
lastSavedBody string
|
||||
}
|
||||
|
||||
// containerFingerprint is the subset of container data that the Caddyfile generator depends on.
|
||||
// Comparing fingerprints lets the controller skip no-op regenerations.
|
||||
// containerFingerprint contains container identity and routing inputs that can change the generated Caddyfile.
|
||||
// It excludes incidental Docker state so unrelated container updates do not cause a Caddy reload.
|
||||
type containerFingerprint struct {
|
||||
ID string
|
||||
IP netip.Addr
|
||||
@@ -56,7 +70,8 @@ func (f containerFingerprint) Equal(other containerFingerprint) bool {
|
||||
f.CaddyConfig == other.CaddyConfig
|
||||
}
|
||||
|
||||
func NewController(machineID, configDir, adminSock string, store *store.Store) (*Controller, error) {
|
||||
func NewController(machineID string, service *Service, adminSock string, store *store.Store) (*Controller, error) {
|
||||
configDir := service.configDir
|
||||
if err := os.MkdirAll(configDir, 0o750); err != nil {
|
||||
return nil, fmt.Errorf("create directory for Caddy configuration '%s': %w", configDir, err)
|
||||
}
|
||||
@@ -67,16 +82,21 @@ func NewController(machineID, configDir, adminSock string, store *store.Store) (
|
||||
log := slog.With("component", "caddy-controller")
|
||||
client := NewCaddyAdminClient(adminSock)
|
||||
|
||||
// generator is initialised by Run() once the machine name is resolved from the store.
|
||||
// The generator is initialised by Run() after resolving the machine name from the store.
|
||||
return &Controller{
|
||||
machineID: machineID,
|
||||
caddyfilePath: filepath.Join(configDir, "Caddyfile"),
|
||||
service: service,
|
||||
client: client,
|
||||
store: store,
|
||||
log: log,
|
||||
fileGroup: CaddyGroup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run reconciles on container changes and every periodicReconciliationInterval. Failed work is retried every second,
|
||||
// but repeated retry failures are logged only during the periodic check.
|
||||
// The periodic check also covers missed events and outstanding failures after the daemon restarts.
|
||||
func (c *Controller) Run(ctx context.Context) error {
|
||||
// Default the machine name to the machine ID so the Caddyfile header still carries a stable identifier if
|
||||
// the store lookup fails.
|
||||
@@ -95,14 +115,35 @@ func (c *Controller) Run(ctx context.Context) error {
|
||||
}
|
||||
c.log.Info("Subscribed to container changes in the cluster to generate Caddy configuration.")
|
||||
|
||||
containers = filterHealthyContainers(containers)
|
||||
c.generateAndLoadCaddyfile(ctx, containers)
|
||||
var retryC <-chan time.Time
|
||||
periodic := time.NewTicker(periodicReconciliationInterval)
|
||||
defer periodic.Stop()
|
||||
|
||||
// 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)
|
||||
handleResult := func(err error, logFailure bool) {
|
||||
c.service.setReconciliationResult(err)
|
||||
if err == nil {
|
||||
retryC = nil
|
||||
return
|
||||
}
|
||||
if logFailure {
|
||||
c.log.Error("Failed to reconcile Caddy configuration, will retry.", "err", err)
|
||||
}
|
||||
retryC = time.After(time.Second)
|
||||
}
|
||||
|
||||
reconcile := func(logFailure bool) {
|
||||
ctrs, err := c.store.ListContainers(ctx, store.ListOptions{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("list containers: %w", err)
|
||||
} else {
|
||||
err = c.generateAndLoadCaddyfile(ctx, ctrs)
|
||||
}
|
||||
handleResult(err, logFailure)
|
||||
}
|
||||
|
||||
// The subscription supplies an initial snapshot, so the first attempt need not wait for a change event.
|
||||
handleResult(c.generateAndLoadCaddyfile(ctx, containers), true)
|
||||
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-changes:
|
||||
@@ -111,25 +152,18 @@ func (c *Controller) Run(ctx context.Context) error {
|
||||
}
|
||||
c.log.Debug("Cluster containers changed, regenerating 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)
|
||||
}
|
||||
reconcile(true)
|
||||
case <-periodic.C:
|
||||
reconcile(true)
|
||||
case <-retryC:
|
||||
reconcile(false)
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filterHealthyContainers filters out unhealthy and hook containers.
|
||||
// filterHealthyContainers filters out unhealthy, hook, and caddy containers.
|
||||
// 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.
|
||||
@@ -139,6 +173,9 @@ func filterHealthyContainers(containers []store.ContainerRecord) []store.Contain
|
||||
if cr.Container.IsHook() {
|
||||
continue
|
||||
}
|
||||
if cr.Container.ServiceName() == CaddyServiceName {
|
||||
continue
|
||||
}
|
||||
if cr.Container.Healthy() {
|
||||
healthy = append(healthy, cr)
|
||||
}
|
||||
@@ -146,65 +183,96 @@ func filterHealthyContainers(containers []store.ContainerRecord) []store.Contain
|
||||
return healthy
|
||||
}
|
||||
|
||||
// generateAndLoadCaddyfile regenerates the Caddyfile from the given containers and loads it into the local Caddy
|
||||
// if available.
|
||||
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()
|
||||
|
||||
// Skip regeneration when Caddy is available and the containers since the last successful load haven't changed.
|
||||
// When Caddy is unavailable we still regenerate to keep the Caddyfile on disk updated.
|
||||
fingerprint := fingerprintContainers(containers)
|
||||
if caddyAvailable && slices.EqualFunc(fingerprint, c.lastFingerprint, containerFingerprint.Equal) {
|
||||
c.log.Debug("Caddy configuration is unchanged.", "path", c.caddyfilePath)
|
||||
return
|
||||
// generateAndLoadCaddyfile reconciles the shared Caddyfile for the selected local Caddy container. A full candidate
|
||||
// reaches disk only after Caddy accepts it. Without an admin endpoint, the controller keeps a saved full file intact
|
||||
// and writes a bootstrap config only when the file is absent or already a bootstrap.
|
||||
func (c *Controller) generateAndLoadCaddyfile(ctx context.Context, containers []store.ContainerRecord) error {
|
||||
caddyCtr := selectLocalCaddyContainer(containers, c.machineID)
|
||||
if caddyCtr == nil {
|
||||
// Caddy is not running locally which means there is no reliable source for the global config, skipping.
|
||||
return nil
|
||||
}
|
||||
|
||||
caddyfile, err := c.generator.Generate(ctx, containers, caddyAvailable)
|
||||
if err != nil {
|
||||
c.log.Error("Failed to generate Caddyfile configuration.", "err", err)
|
||||
return
|
||||
saved, readErr := os.ReadFile(c.caddyfilePath)
|
||||
if readErr != nil && !errors.Is(readErr, os.ErrNotExist) {
|
||||
return fmt.Errorf("read saved Caddyfile: %w", readErr)
|
||||
}
|
||||
haveSaved := readErr == nil
|
||||
|
||||
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.writeCaddyfileIfChanged(caddyfile); err != nil {
|
||||
c.log.Error("Failed to write Caddyfile to disk.", "err", err)
|
||||
return
|
||||
healthyCtrs := filterHealthyContainers(containers)
|
||||
fingerprint := fingerprintContainers(healthyCtrs)
|
||||
|
||||
if !caddyCtr.State.Running || !c.client.IsAvailable() {
|
||||
if haveSaved && !isBootstrapCaddyfile(string(saved)) {
|
||||
// Caddy may need this file to restart or roll back. A hosts-only replacement could drop
|
||||
// storage or other global settings while the admin API is unavailable.
|
||||
if caddyCtr.State.Running {
|
||||
return fmt.Errorf("caddy admin socket unavailable, preserving saved Caddyfile")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
c.log.Debug("Caddy is not running on this machine, skipping configuration load.", "path", c.caddyfilePath)
|
||||
return
|
||||
|
||||
bootstrap, err := c.generator.Generate(ctx, *caddyCtr, healthyCtrs, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate Caddy bootstrap config: %w", err)
|
||||
}
|
||||
if err = c.writeCaddyfileIfChanged(bootstrap); err != nil {
|
||||
return fmt.Errorf("save Caddy bootstrap config: %w", err)
|
||||
}
|
||||
if caddyCtr.State.Running {
|
||||
return fmt.Errorf("caddy admin socket unavailable, bootstrap config saved")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 haveSaved &&
|
||||
caddyfileBody(string(saved)) == c.lastSavedBody &&
|
||||
!isBootstrapCaddyfile(string(saved)) &&
|
||||
c.lastCaddyCtrID == caddyCtr.ID &&
|
||||
c.lastStartedAt == caddyCtr.State.StartedAt &&
|
||||
c.lastGlobal == caddyCtr.ServiceSpec.CaddyConfig() &&
|
||||
slices.EqualFunc(fingerprint, c.lastFingerprint, containerFingerprint.Equal) {
|
||||
// No changes to the inputs that affect the generated Caddyfile, so no reload is needed.
|
||||
return nil
|
||||
}
|
||||
|
||||
caddyfile, err := c.generator.Generate(ctx, *caddyCtr, healthyCtrs, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate Caddyfile: %w", err)
|
||||
}
|
||||
// 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)
|
||||
// Mark the cache stale so the next container change retries the load even if the container set is unchanged.
|
||||
c.lastFingerprint = nil
|
||||
// Don't write invalid config to disk.
|
||||
return
|
||||
return fmt.Errorf("load Caddyfile: %w", err)
|
||||
}
|
||||
if err = c.writeCaddyfileIfChanged(caddyfile); err != nil {
|
||||
return fmt.Errorf("save loaded Caddyfile: %w", err)
|
||||
}
|
||||
c.lastFingerprint = fingerprint
|
||||
|
||||
// Config loaded successfully, now write it to disk.
|
||||
if err = c.writeCaddyfileIfChanged(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. The next regeneration retries the disk write.
|
||||
return
|
||||
}
|
||||
|
||||
c.lastCaddyCtrID = caddyCtr.ID
|
||||
c.lastStartedAt = caddyCtr.State.StartedAt
|
||||
c.lastGlobal = caddyCtr.ServiceSpec.CaddyConfig()
|
||||
c.lastSavedBody = caddyfileBody(caddyfile)
|
||||
c.log.Info("New Caddy configuration loaded into local Caddy instance.", "path", c.caddyfilePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isBootstrapCaddyfile distinguishes a reduced startup file from a full file that must survive Caddy outage.
|
||||
// It also recognizes the older daemon's offline file. That file omitted every x-caddy block, including globals,
|
||||
// so treating it as a protected full file would preserve the configuration-loss bug:
|
||||
// https://github.com/psviderski/uncloud/issues/412
|
||||
func isBootstrapCaddyfile(caddyfile string) bool {
|
||||
return strings.Contains(caddyfile, bootstrapMarker) || strings.Contains(caddyfile, legacyBootstrapMarker)
|
||||
}
|
||||
|
||||
// fingerprintContainers returns a fingerprint of containers that the Caddyfile generator depends on.
|
||||
func fingerprintContainers(containers []store.ContainerRecord) []containerFingerprint {
|
||||
fingerprints := make([]containerFingerprint, len(containers))
|
||||
for i, cr := range containers {
|
||||
// Ignore ports parsing error as not much we can do about it. The generator just logs them and continues.
|
||||
// Ignore ports parsing error as not much we can do about it. The generator just logs them and skips
|
||||
// the container.
|
||||
ports, _ := cr.Container.ServicePorts()
|
||||
fingerprints[i] = containerFingerprint{
|
||||
ID: cr.Container.ID,
|
||||
@@ -220,21 +288,71 @@ func fingerprintContainers(containers []store.ContainerRecord) []containerFinger
|
||||
return fingerprints
|
||||
}
|
||||
|
||||
// writeCaddyfileIfChanged writes the Caddyfile content to disk with proper permissions only if its body differs
|
||||
// from the last successfully written content. The first line of the Caddyfile carries a generation timestamp that
|
||||
// changes on every regeneration, so it's excluded from the comparison to avoid redundant writes.
|
||||
// selectLocalCaddyContainer chooses which local Caddy container supplies global Caddy config. A running container
|
||||
// takes precedence over a newer stopped replacement, even if the running container is unhealthy. If two revisions
|
||||
// run at once, the shared admin socket does not identify its owner, so this choice remains best effort.
|
||||
func selectLocalCaddyContainer(records []store.ContainerRecord, machineID string) *api.ServiceContainer {
|
||||
var selected *api.ServiceContainer
|
||||
for _, cr := range records {
|
||||
ctr := cr.Container
|
||||
if cr.MachineID != machineID || ctr.ServiceName() != CaddyServiceName || ctr.IsHook() {
|
||||
continue
|
||||
}
|
||||
if selected == nil {
|
||||
selected = &ctr
|
||||
continue
|
||||
}
|
||||
if ctr.State.Running != selected.State.Running {
|
||||
if ctr.State.Running {
|
||||
selected = &ctr
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ctr.CreatedTime().Compare(selected.CreatedTime()) > 0 {
|
||||
selected = &ctr
|
||||
}
|
||||
}
|
||||
|
||||
return selected
|
||||
}
|
||||
|
||||
// writeCaddyfileIfChanged atomically replaces the saved Caddyfile only if its body differs from the new content.
|
||||
func (c *Controller) writeCaddyfileIfChanged(caddyfile string) error {
|
||||
if caddyfileBody(caddyfile) == caddyfileBody(c.lastCaddyfile) {
|
||||
saved, err := os.ReadFile(c.caddyfilePath)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("read Caddyfile '%s': %w", c.caddyfilePath, err)
|
||||
}
|
||||
if err == nil && caddyfileBody(caddyfile) == caddyfileBody(string(saved)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.WriteFile(c.caddyfilePath, []byte(caddyfile), 0o640); err != nil {
|
||||
return fmt.Errorf("write Caddyfile to file '%s': %w", c.caddyfilePath, err)
|
||||
dir := filepath.Dir(c.caddyfilePath)
|
||||
tmp, err := os.CreateTemp(dir, ".Caddyfile-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temporary Caddyfile: %w", err)
|
||||
}
|
||||
if err := fs.Chown(c.caddyfilePath, "", CaddyGroup); err != nil {
|
||||
return fmt.Errorf("change owner of Caddyfile '%s': %w", c.caddyfilePath, err)
|
||||
defer os.Remove(tmp.Name())
|
||||
defer tmp.Close()
|
||||
|
||||
if err = tmp.Chmod(0o640); err != nil {
|
||||
return fmt.Errorf("set temporary Caddyfile permissions: %w", err)
|
||||
}
|
||||
if err = fs.Chown(tmp.Name(), "", c.fileGroup); err != nil {
|
||||
return fmt.Errorf("change owner of temporary Caddyfile: %w", err)
|
||||
}
|
||||
|
||||
if _, err = tmp.WriteString(caddyfile); err != nil {
|
||||
return fmt.Errorf("write temporary Caddyfile: %w", err)
|
||||
}
|
||||
if err = tmp.Sync(); err != nil {
|
||||
return fmt.Errorf("sync temporary Caddyfile: %w", err)
|
||||
}
|
||||
if err = tmp.Close(); err != nil {
|
||||
return fmt.Errorf("close temporary Caddyfile: %w", err)
|
||||
}
|
||||
if err = os.Rename(tmp.Name(), c.caddyfilePath); err != nil {
|
||||
return fmt.Errorf("replace Caddyfile '%s': %w", c.caddyfilePath, err)
|
||||
}
|
||||
c.lastCaddyfile = caddyfile
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -247,30 +365,3 @@ func caddyfileBody(caddyfile string) string {
|
||||
}
|
||||
return caddyfile
|
||||
}
|
||||
|
||||
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,14 +1,488 @@
|
||||
package caddyconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testController(t *testing.T, socketPath string) *Controller {
|
||||
t.Helper()
|
||||
group, err := user.LookupGroupId(strconv.Itoa(os.Getegid()))
|
||||
require.NoError(t, err)
|
||||
path := filepath.Join(t.TempDir(), "Caddyfile")
|
||||
client := NewCaddyAdminClient(socketPath)
|
||||
return &Controller{
|
||||
machineID: "test-machine-id",
|
||||
caddyfilePath: path,
|
||||
generator: NewCaddyfileGenerator("test-machine-id", "test-machine", client, nil),
|
||||
client: client,
|
||||
log: slog.Default(),
|
||||
fileGroup: group.Name,
|
||||
}
|
||||
}
|
||||
|
||||
type testCaddyAdmin struct {
|
||||
mu sync.Mutex
|
||||
adaptRequests []string
|
||||
loadCount int
|
||||
rejectAdaptContaining string
|
||||
rejectLoad bool
|
||||
}
|
||||
|
||||
func (a *testCaddyAdmin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/adapt":
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
a.mu.Lock()
|
||||
a.adaptRequests = append(a.adaptRequests, string(body))
|
||||
reject := a.rejectAdaptContaining != "" && strings.Contains(string(body), a.rejectAdaptContaining)
|
||||
a.mu.Unlock()
|
||||
if reject {
|
||||
http.Error(w, "invalid Caddyfile", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, _ = io.WriteString(w, `{"result":{}}`)
|
||||
case "/load":
|
||||
a.mu.Lock()
|
||||
a.loadCount++
|
||||
reject := a.rejectLoad
|
||||
a.mu.Unlock()
|
||||
if reject {
|
||||
http.Error(w, "load rejected", http.StatusBadRequest)
|
||||
}
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *testCaddyAdmin) snapshot() ([]string, int) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return append([]string(nil), a.adaptRequests...), a.loadCount
|
||||
}
|
||||
|
||||
func (a *testCaddyAdmin) setRejectLoad(reject bool) {
|
||||
a.mu.Lock()
|
||||
a.rejectLoad = reject
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestController_GenerateAndLoadCaddyfile(t *testing.T) {
|
||||
t.Run("keeps the saved file when there is no local Caddy container", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
saved := "# previous full Caddyfile\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
remote := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{ remote }",
|
||||
"other-machine", time.Now())
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), []store.ContainerRecord{remote}))
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
})
|
||||
|
||||
t.Run("does not publish a bootstrap with an invalid global template", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{{upstreams",
|
||||
"test-machine-id", time.Now())
|
||||
|
||||
require.ErrorContains(t, controller.generateAndLoadCaddyfile(context.Background(),
|
||||
[]store.ContainerRecord{caddyCtr}), "render template")
|
||||
_, err := os.Stat(controller.caddyfilePath)
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
})
|
||||
|
||||
t.Run("bootstraps unhealthy Caddy with its globals and healthy routes", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
caddyCtr.Container.State.Health = &container.Health{Status: "unhealthy"}
|
||||
newerStopped := newContainerRecordWithCaddyConfig("caddy", "10.210.0.6", "{\n\tstorage replacement\n}",
|
||||
"test-machine-id", time.Now().Add(time.Minute))
|
||||
newerStopped.Container.State.Running = false
|
||||
app := newContainerRecordWithPorts("web", "10.210.0.3", []string{"app.example.com:8080/http"},
|
||||
"test-machine-id")
|
||||
app.Container.ServiceSpec.Caddy = &api.CaddySpec{Config: "custom.example.com { respond app }"}
|
||||
unhealthy := newContainerRecordWithPorts("unhealthy", "10.210.0.4",
|
||||
[]string{"unhealthy.example.com:8080/http"}, "test-machine-id")
|
||||
unhealthy.Container.State.Health = &container.Health{Status: "unhealthy"}
|
||||
hook := newContainerRecordWithPorts("hook", "10.210.0.5",
|
||||
[]string{"hook.example.com:8080/http"}, "test-machine-id")
|
||||
hook.Container.Config.Labels[api.LabelHook] = "pre-deploy"
|
||||
|
||||
err := controller.generateAndLoadCaddyfile(context.Background(),
|
||||
[]store.ContainerRecord{newerStopped, caddyCtr, app, unhealthy, hook})
|
||||
require.ErrorContains(t, err, "admin socket unavailable")
|
||||
saved, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(saved), bootstrapMarker+caddyCtr.Container.ID)
|
||||
assert.Contains(t, string(saved), "storage uncloud")
|
||||
assert.NotContains(t, string(saved), "storage replacement")
|
||||
assert.Contains(t, string(saved), "app.example.com")
|
||||
assert.NotContains(t, string(saved), "custom.example.com")
|
||||
assert.NotContains(t, string(saved), "unhealthy.example.com")
|
||||
assert.NotContains(t, string(saved), "hook.example.com")
|
||||
})
|
||||
|
||||
t.Run("replaces a legacy offline file with a bootstrap containing globals", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
legacy := "# generated by older daemon\nhttp:// { respond ok }\n" + legacyBootstrapMarker + "\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(legacy), 0o640))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
|
||||
err := controller.generateAndLoadCaddyfile(context.Background(), []store.ContainerRecord{caddyCtr})
|
||||
require.ErrorContains(t, err, "admin socket unavailable")
|
||||
saved, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(saved), "storage uncloud")
|
||||
assert.Contains(t, string(saved), bootstrapMarker+caddyCtr.Container.ID)
|
||||
assert.NotContains(t, string(saved), legacyBootstrapMarker)
|
||||
})
|
||||
|
||||
t.Run("saves a bootstrap for a stopped Caddy container with no file", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
caddyCtr.Container.State.Running = false
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), []store.ContainerRecord{caddyCtr}))
|
||||
saved, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(saved), bootstrapMarker+caddyCtr.Container.ID)
|
||||
assert.Contains(t, string(saved), "storage uncloud")
|
||||
})
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
running bool
|
||||
}{
|
||||
{name: "preserves a full file when the admin socket is unavailable", running: true},
|
||||
{name: "preserves a stopped container's full file", running: false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
saved := "{\n\tstorage uncloud\n}\n\nold.example.com { respond old }\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage new\n}",
|
||||
"test-machine-id", time.Now())
|
||||
caddyCtr.Container.State.Running = tc.running
|
||||
app := newContainerRecordWithPorts("web", "10.210.0.3", []string{"new.example.com:8080/http"},
|
||||
"test-machine-id")
|
||||
|
||||
err := controller.generateAndLoadCaddyfile(context.Background(), []store.ContainerRecord{caddyCtr, app})
|
||||
if tc.running {
|
||||
require.ErrorContains(t, err, "admin socket unavailable")
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("loads a full file and reloads after Caddy restarts", func(t *testing.T) {
|
||||
admin := &testCaddyAdmin{}
|
||||
controller := testController(t, testAdminServer(t, admin.ServeHTTP))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
caddyCtr.Container.State.Health = &container.Health{Status: "unhealthy"}
|
||||
app := newContainerRecordWithCaddyConfig("web", "10.210.0.3", "app.example.com { respond app }",
|
||||
"test-machine-id", time.Now())
|
||||
records := []store.ContainerRecord{caddyCtr, app}
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
saved, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(saved), "storage uncloud")
|
||||
assert.Contains(t, string(saved), "app.example.com")
|
||||
assert.NotContains(t, string(saved), bootstrapMarker)
|
||||
adapted, loads := admin.snapshot()
|
||||
require.NotEmpty(t, adapted)
|
||||
assert.Contains(t, adapted[0], "storage uncloud")
|
||||
assert.Equal(t, 1, loads)
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
_, loads = admin.snapshot()
|
||||
assert.Equal(t, 1, loads, "unchanged inputs should not reload Caddy")
|
||||
|
||||
records[0].Container.State.StartedAt = time.Now().UTC().Format(time.RFC3339Nano)
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
_, loads = admin.snapshot()
|
||||
assert.Equal(t, 2, loads, "a restarted Caddy process needs the full configuration")
|
||||
})
|
||||
|
||||
t.Run("reloads when an application route changes", func(t *testing.T) {
|
||||
admin := &testCaddyAdmin{}
|
||||
controller := testController(t, testAdminServer(t, admin.ServeHTTP))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
app := newContainerRecordWithPorts("web", "10.210.0.3", []string{"app.example.com:8080/http"},
|
||||
"test-machine-id")
|
||||
records := []store.ContainerRecord{caddyCtr, app}
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
_, loads := admin.snapshot()
|
||||
assert.Equal(t, 1, loads)
|
||||
|
||||
records[1] = newContainerRecordWithPorts("web", "10.210.0.4",
|
||||
[]string{"app.example.com:8080/http"}, "test-machine-id")
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
saved, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(saved), "10.210.0.4:8080")
|
||||
assert.NotContains(t, string(saved), "10.210.0.3:8080")
|
||||
_, loads = admin.snapshot()
|
||||
assert.Equal(t, 2, loads)
|
||||
})
|
||||
|
||||
t.Run("rejects invalid globals without loading or replacing the saved file", func(t *testing.T) {
|
||||
admin := &testCaddyAdmin{rejectAdaptContaining: "storage rejected"}
|
||||
controller := testController(t, testAdminServer(t, admin.ServeHTTP))
|
||||
saved := "{\n\tstorage uncloud\n}\n\nold.example.com { respond old }\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage rejected\n}",
|
||||
"test-machine-id", time.Now())
|
||||
|
||||
err := controller.generateAndLoadCaddyfile(context.Background(), []store.ContainerRecord{caddyCtr})
|
||||
require.ErrorContains(t, err, "validate user-defined global Caddy config")
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
_, loads := admin.snapshot()
|
||||
assert.Zero(t, loads)
|
||||
})
|
||||
|
||||
t.Run("skips invalid application custom config", func(t *testing.T) {
|
||||
admin := &testCaddyAdmin{rejectAdaptContaining: "invalid.example.com"}
|
||||
controller := testController(t, testAdminServer(t, admin.ServeHTTP))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
invalid := newContainerRecordWithCaddyConfig("invalid", "10.210.0.3",
|
||||
"invalid.example.com { respond bad }", "test-machine-id", time.Now())
|
||||
valid := newContainerRecordWithCaddyConfig("valid", "10.210.0.4",
|
||||
"valid.example.com { respond good }", "test-machine-id", time.Now())
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(),
|
||||
[]store.ContainerRecord{caddyCtr, invalid, valid}))
|
||||
saved, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(saved), "storage uncloud")
|
||||
assert.Contains(t, string(saved), "valid.example.com")
|
||||
assert.NotContains(t, string(saved), "invalid.example.com")
|
||||
assert.Contains(t, string(saved), "Skipped invalid user-defined configs")
|
||||
_, loads := admin.snapshot()
|
||||
assert.Equal(t, 1, loads)
|
||||
})
|
||||
|
||||
t.Run("keeps the saved file when application validation cannot complete", func(t *testing.T) {
|
||||
var loads atomic.Int64
|
||||
socket := testAdminServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/adapt":
|
||||
http.Error(w, "adapter unavailable", http.StatusInternalServerError)
|
||||
case "/load":
|
||||
loads.Add(1)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
controller := testController(t, socket)
|
||||
saved := "old.example.com { respond old }\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "",
|
||||
"test-machine-id", time.Now())
|
||||
app := newContainerRecordWithCaddyConfig("web", "10.210.0.3", "new.example.com { respond new }",
|
||||
"test-machine-id", time.Now())
|
||||
|
||||
err := controller.generateAndLoadCaddyfile(context.Background(), []store.ContainerRecord{caddyCtr, app})
|
||||
require.ErrorContains(t, err, "validate Caddy config for service")
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
assert.Zero(t, loads.Load())
|
||||
})
|
||||
|
||||
t.Run("retries a rejected load without changing inputs", func(t *testing.T) {
|
||||
admin := &testCaddyAdmin{rejectLoad: true}
|
||||
controller := testController(t, testAdminServer(t, admin.ServeHTTP))
|
||||
saved := "{\n\tstorage old\n}\n\nold.example.com { respond old }\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
records := []store.ContainerRecord{caddyCtr}
|
||||
|
||||
require.ErrorContains(t, controller.generateAndLoadCaddyfile(context.Background(), records), "load Caddyfile")
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
admin.setRejectLoad(false)
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
got, err = os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(got), "storage uncloud")
|
||||
_, loads := admin.snapshot()
|
||||
assert.Equal(t, 2, loads)
|
||||
})
|
||||
|
||||
t.Run("retries saving after a successful load", func(t *testing.T) {
|
||||
admin := &testCaddyAdmin{}
|
||||
controller := testController(t, testAdminServer(t, admin.ServeHTTP))
|
||||
group := controller.fileGroup
|
||||
controller.fileGroup = "uncloud-test-group-that-does-not-exist"
|
||||
saved := "{\n\tstorage old\n}\n\nold.example.com { respond old }\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
records := []store.ContainerRecord{caddyCtr}
|
||||
|
||||
require.ErrorContains(t, controller.generateAndLoadCaddyfile(context.Background(), records), "save loaded Caddyfile")
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
controller.fileGroup = group
|
||||
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
got, err = os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(got), "storage uncloud")
|
||||
_, loads := admin.snapshot()
|
||||
assert.Equal(t, 2, loads)
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
_, loads = admin.snapshot()
|
||||
assert.Equal(t, 2, loads, "a loaded and saved revision should not reload")
|
||||
})
|
||||
|
||||
t.Run("loads a saved bootstrap when the admin socket becomes available", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
caddyCtr := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{\n\tstorage uncloud\n}",
|
||||
"test-machine-id", time.Now())
|
||||
records := []store.ContainerRecord{caddyCtr}
|
||||
require.Error(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
bootstrap, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(bootstrap), bootstrapMarker)
|
||||
|
||||
admin := &testCaddyAdmin{}
|
||||
socket := testAdminServer(t, admin.ServeHTTP)
|
||||
controller.client = NewCaddyAdminClient(socket)
|
||||
controller.generator = NewCaddyfileGenerator("test-machine-id", "test-machine", controller.client, nil)
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
full, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, string(full), bootstrapMarker)
|
||||
_, loads := admin.snapshot()
|
||||
assert.Equal(t, 1, loads)
|
||||
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, bootstrap, 0o640))
|
||||
require.NoError(t, controller.generateAndLoadCaddyfile(context.Background(), records))
|
||||
_, loads = admin.snapshot()
|
||||
assert.Equal(t, 2, loads, "a saved bootstrap is not an applied full configuration")
|
||||
})
|
||||
}
|
||||
|
||||
func TestController_WriteCaddyfileIfChanged(t *testing.T) {
|
||||
t.Run("publishes a complete file with the configured group", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
caddyfile := "# Generated now\n{\n\tstorage uncloud\n}\n"
|
||||
|
||||
require.NoError(t, controller.writeCaddyfileIfChanged(caddyfile))
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, caddyfile, string(got))
|
||||
info, err := os.Stat(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, os.FileMode(0o640), info.Mode().Perm())
|
||||
})
|
||||
|
||||
t.Run("does not replace a file when only the timestamp changes", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
saved := "# Generated yesterday\nsite.example.com { respond ok }\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
before, err := os.Stat(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, controller.writeCaddyfileIfChanged(
|
||||
"# Generated today\nsite.example.com { respond ok }\n"))
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
after, err := os.Stat(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, os.SameFile(before, after), "an unchanged body should not replace the file")
|
||||
})
|
||||
|
||||
t.Run("preserves the previous file when publication fails", func(t *testing.T) {
|
||||
controller := testController(t, filepath.Join(t.TempDir(), "missing.sock"))
|
||||
controller.fileGroup = "uncloud-test-group-that-does-not-exist"
|
||||
saved := "# previous\nsite.example.com { respond old }\n"
|
||||
require.NoError(t, os.WriteFile(controller.caddyfilePath, []byte(saved), 0o640))
|
||||
|
||||
err := controller.writeCaddyfileIfChanged("# next\nsite.example.com { respond new }\n")
|
||||
require.ErrorContains(t, err, "change owner of temporary Caddyfile")
|
||||
got, err := os.ReadFile(controller.caddyfilePath)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, saved, string(got))
|
||||
tmpFiles, err := filepath.Glob(filepath.Join(filepath.Dir(controller.caddyfilePath), ".Caddyfile-*"))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, tmpFiles)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSelectLocalCaddyContainer(t *testing.T) {
|
||||
t.Run("prefers a running unhealthy container over a newer stopped one", func(t *testing.T) {
|
||||
older := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{ older }",
|
||||
"test-machine-id", time.Now().Add(-time.Hour))
|
||||
older.Container.State.Health = &container.Health{Status: "unhealthy"}
|
||||
newer := newContainerRecordWithCaddyConfig("caddy", "10.210.0.3", "{ newer }",
|
||||
"test-machine-id", time.Now())
|
||||
newer.Container.State.Running = false
|
||||
|
||||
selected := selectLocalCaddyContainer([]store.ContainerRecord{newer, older}, "test-machine-id")
|
||||
require.NotNil(t, selected)
|
||||
assert.Equal(t, older.Container.ID, selected.ID)
|
||||
})
|
||||
|
||||
t.Run("ignores remote and hook containers", func(t *testing.T) {
|
||||
local := newContainerRecordWithCaddyConfig("caddy", "10.210.0.2", "{ local }",
|
||||
"test-machine-id", time.Now().Add(-time.Hour))
|
||||
remote := newContainerRecordWithCaddyConfig("caddy", "10.210.0.3", "{ remote }",
|
||||
"other-machine", time.Now())
|
||||
hook := newContainerRecordWithCaddyConfig("caddy", "10.210.0.4", "{ hook }",
|
||||
"test-machine-id", time.Now())
|
||||
hook.Container.Config.Labels[api.LabelHook] = "pre-deploy"
|
||||
|
||||
selected := selectLocalCaddyContainer([]store.ContainerRecord{remote, hook, local}, "test-machine-id")
|
||||
require.NotNil(t, selected)
|
||||
assert.Equal(t, local.Container.ID, selected.ID)
|
||||
})
|
||||
}
|
||||
|
||||
// TestContainerFingerprint_EqualCoversAllFields is a guard: when a field is added to containerFingerprint,
|
||||
// Equal must also compare it. A mutation of any single field should flip equality to false. If this test fails
|
||||
// after adding a field, update Equal to include it.
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
package caddyconfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
)
|
||||
|
||||
func GenerateJSONConfig(containers []api.ServiceContainer, verifyResponse string) (*caddy.Config, error) {
|
||||
httpHostUpstreams, httpsHostUpstreams := httpUpstreamsFromPorts(containers)
|
||||
|
||||
var warnings []caddyconfig.Warning
|
||||
servers := make(map[string]*caddyhttp.Server)
|
||||
servers["http"] = &caddyhttp.Server{
|
||||
Listen: []string{fmt.Sprintf(":%d", caddyhttp.DefaultHTTPPort)},
|
||||
// All http requests to this server are logged to the default logger.
|
||||
Logs: &caddyhttp.ServerLogConfig{},
|
||||
Routes: append(
|
||||
hostUpstreamsToRoutes(httpHostUpstreams, &warnings),
|
||||
// Add a route to respond with a static verification response at the /.uncloud-verify path.
|
||||
verificationRoute(verifyResponse, &warnings),
|
||||
),
|
||||
}
|
||||
servers["https"] = &caddyhttp.Server{
|
||||
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),
|
||||
}
|
||||
|
||||
httpApp := caddyhttp.App{
|
||||
Servers: servers,
|
||||
}
|
||||
config := &caddy.Config{
|
||||
AppsRaw: caddy.ModuleMap{
|
||||
"http": caddyconfig.JSON(httpApp, &warnings),
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(warnings) > 0 {
|
||||
// warnings only contains errors from JSON marshaling, which are highly unlikely with correct code.
|
||||
for _, w := range warnings {
|
||||
err = errors.Join(err, errors.New(w.Message))
|
||||
}
|
||||
return nil, fmt.Errorf("marshal Caddy configuration: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// hostUpstreamsToRoutes converts a map of hostnames to upstreams to a list of Caddy routes.
|
||||
func hostUpstreamsToRoutes(hostUpstreams map[string][]string, warnings *[]caddyconfig.Warning) []caddyhttp.Route {
|
||||
// Sort hostnames for deterministic output.
|
||||
hostnames := slices.Collect(maps.Keys(hostUpstreams))
|
||||
slices.Sort(hostnames)
|
||||
|
||||
routes := make([]caddyhttp.Route, 0, len(hostUpstreams))
|
||||
for _, hostname := range hostnames {
|
||||
upstreams := hostUpstreams[hostname]
|
||||
upstreamPool := make([]*reverseproxy.Upstream, len(upstreams))
|
||||
for i, upstream := range upstreams {
|
||||
upstreamPool[i] = &reverseproxy.Upstream{
|
||||
Dial: upstream,
|
||||
}
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
||||
routes = append(routes, caddyhttp.Route{
|
||||
MatcherSetsRaw: caddyhttp.RawMatcherSets{
|
||||
{
|
||||
"host": caddyconfig.JSON(caddyhttp.MatchHost{hostname}, warnings),
|
||||
},
|
||||
},
|
||||
HandlersRaw: []json.RawMessage{
|
||||
caddyconfig.JSONModuleObject(handler, "handler", "reverse_proxy", warnings),
|
||||
},
|
||||
})
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// verificationRoute returns a Caddy route that responds with the given static response at the /.uncloud-verify path.
|
||||
func verificationRoute(response string, warnings *[]caddyconfig.Warning) caddyhttp.Route {
|
||||
// Return the following route:
|
||||
// {
|
||||
// "match": [
|
||||
// {
|
||||
// "path": [
|
||||
// "/.uncloud-verify"
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// "handle": [
|
||||
// {
|
||||
// "handler": "static_response",
|
||||
// "body": "<response>",
|
||||
// "status_code": 200
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
staticResponse := caddyhttp.StaticResponse{
|
||||
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusOK)),
|
||||
Body: response,
|
||||
}
|
||||
|
||||
return caddyhttp.Route{
|
||||
MatcherSetsRaw: caddyhttp.RawMatcherSets{
|
||||
{
|
||||
"path": caddyconfig.JSON(caddyhttp.MatchPath{VerifyPath}, warnings),
|
||||
},
|
||||
},
|
||||
HandlersRaw: []json.RawMessage{
|
||||
caddyconfig.JSONModuleObject(staticResponse, "handler", "static_response", warnings),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
package caddyconfig
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenerateJSONConfig(t *testing.T) {
|
||||
configWithoutServices := `{
|
||||
"servers": {
|
||||
"http": {
|
||||
"listen": [":80"],
|
||||
"routes": [{
|
||||
"match": [{"path": ["/.uncloud-verify"]}],
|
||||
"handle": [{
|
||||
"body": "verification-response-body",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}]
|
||||
}],
|
||||
"logs": {}
|
||||
},
|
||||
"https": {
|
||||
"listen": [":443"],
|
||||
"logs": {}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []api.ServiceContainer
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty containers",
|
||||
containers: []api.ServiceContainer{},
|
||||
want: configWithoutServices,
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "HTTP container",
|
||||
containers: []api.ServiceContainer{
|
||||
newContainer("10.210.0.2", "app.example.com:8080/http"),
|
||||
},
|
||||
want: `{
|
||||
"servers": {
|
||||
"http": {
|
||||
"listen": [":80"],
|
||||
"routes": [
|
||||
{
|
||||
"match": [{"host": ["app.example.com"]}],
|
||||
"handle": [{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"passive": {
|
||||
"fail_duration": 30000000000
|
||||
}
|
||||
},
|
||||
"load_balancing": {
|
||||
"retries": 3
|
||||
},
|
||||
"upstreams": [{"dial": "10.210.0.2:8080"}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"match": [{"path": ["/.uncloud-verify"]}],
|
||||
"handle": [{
|
||||
"body": "verification-response-body",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}]
|
||||
}
|
||||
],
|
||||
"logs": {}
|
||||
},
|
||||
"https": {
|
||||
"listen": [":443"],
|
||||
"logs": {}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "load balancing multiple containers",
|
||||
containers: []api.ServiceContainer{
|
||||
newContainer("10.210.0.2", "app.example.com:8080/http"),
|
||||
newContainer("10.210.0.3", "app.example.com:8080/http"),
|
||||
},
|
||||
want: `{
|
||||
"servers": {
|
||||
"http": {
|
||||
"listen": [":80"],
|
||||
"routes": [
|
||||
{
|
||||
"match": [{"host": ["app.example.com"]}],
|
||||
"handle": [{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"passive": {
|
||||
"fail_duration": 30000000000
|
||||
}
|
||||
},
|
||||
"load_balancing": {
|
||||
"retries": 3
|
||||
},
|
||||
"upstreams": [
|
||||
{"dial": "10.210.0.2:8080"},
|
||||
{"dial": "10.210.0.3:8080"}
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"match": [{"path": ["/.uncloud-verify"]}],
|
||||
"handle": [{
|
||||
"body": "verification-response-body",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}]
|
||||
}
|
||||
],
|
||||
"logs": {}
|
||||
},
|
||||
"https": {
|
||||
"listen": [":443"],
|
||||
"logs": {}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "HTTPS container",
|
||||
containers: []api.ServiceContainer{
|
||||
newContainer("10.210.0.2", "secure.example.com:8000/https"),
|
||||
},
|
||||
want: `{
|
||||
"servers": {
|
||||
"http": {
|
||||
"listen": [":80"],
|
||||
"routes": [
|
||||
{
|
||||
"match": [{"path": ["/.uncloud-verify"]}],
|
||||
"handle": [{
|
||||
"body": "verification-response-body",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}]
|
||||
}
|
||||
],
|
||||
"logs": {}
|
||||
},
|
||||
"https": {
|
||||
"listen": [":443"],
|
||||
"routes": [
|
||||
{
|
||||
"match": [{"host": ["secure.example.com"]}],
|
||||
"handle": [{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"passive": {
|
||||
"fail_duration": 30000000000
|
||||
}
|
||||
},
|
||||
"load_balancing": {
|
||||
"retries": 3
|
||||
},
|
||||
"upstreams": [{"dial": "10.210.0.2:8000"}]
|
||||
}]
|
||||
}
|
||||
],
|
||||
"logs": {}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "mixed HTTP and HTTPS",
|
||||
containers: []api.ServiceContainer{
|
||||
newContainer("10.210.0.2",
|
||||
"app.example.com:8080/http",
|
||||
"web.example.com:8000/http"),
|
||||
newContainer("10.210.0.3",
|
||||
"app.example.com:8080/http",
|
||||
"secure.example.com:8888/https"),
|
||||
newContainer("10.210.0.4",
|
||||
"web.example.com:8000/http",
|
||||
"secure.example.com:8888/https"),
|
||||
newContainer("10.210.0.5",
|
||||
"app.example.com:8080/http",
|
||||
"web.example.com:8000/http",
|
||||
"secure.example.com:8888/https"),
|
||||
},
|
||||
want: `{
|
||||
"servers": {
|
||||
"http": {
|
||||
"listen": [":80"],
|
||||
"routes": [
|
||||
{
|
||||
"match": [{"host": ["app.example.com"]}],
|
||||
"handle": [{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"passive": {
|
||||
"fail_duration": 30000000000
|
||||
}
|
||||
},
|
||||
"load_balancing": {
|
||||
"retries": 3
|
||||
},
|
||||
"upstreams": [
|
||||
{"dial": "10.210.0.2:8080"},
|
||||
{"dial": "10.210.0.3:8080"},
|
||||
{"dial": "10.210.0.5:8080"}
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"match": [{"host": ["web.example.com"]}],
|
||||
"handle": [{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"passive": {
|
||||
"fail_duration": 30000000000
|
||||
}
|
||||
},
|
||||
"load_balancing": {
|
||||
"retries": 3
|
||||
},
|
||||
"upstreams": [
|
||||
{"dial": "10.210.0.2:8000"},
|
||||
{"dial": "10.210.0.4:8000"},
|
||||
{"dial": "10.210.0.5:8000"}
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"match": [{"path": ["/.uncloud-verify"]}],
|
||||
"handle": [{
|
||||
"body": "verification-response-body",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}]
|
||||
}
|
||||
],
|
||||
"logs": {}
|
||||
},
|
||||
"https": {
|
||||
"listen": [":443"],
|
||||
"routes": [
|
||||
{
|
||||
"match": [{"host": ["secure.example.com"]}],
|
||||
"handle": [{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"passive": {
|
||||
"fail_duration": 30000000000
|
||||
}
|
||||
},
|
||||
"load_balancing": {
|
||||
"retries": 3
|
||||
},
|
||||
"upstreams": [
|
||||
{"dial": "10.210.0.3:8888"},
|
||||
{"dial": "10.210.0.4:8888"},
|
||||
{"dial": "10.210.0.5:8888"}
|
||||
]
|
||||
}]
|
||||
}
|
||||
],
|
||||
"logs": {}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "container without uncloud network ignored",
|
||||
containers: []api.ServiceContainer{
|
||||
newContainerWithoutNetwork("ignored.example.com:8080/http"),
|
||||
},
|
||||
want: configWithoutServices,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "container with invalid port ignored",
|
||||
containers: []api.ServiceContainer{
|
||||
newContainer("10.210.0.2", "invalid-port"),
|
||||
},
|
||||
want: configWithoutServices,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "containers with unsupported protocols and host mode ignored",
|
||||
containers: []api.ServiceContainer{
|
||||
newContainer("10.210.0.2", "5000/tcp"),
|
||||
newContainer("10.210.0.3", "5000/udp"),
|
||||
newContainer("10.210.0.4", "80:8080/tcp@host"),
|
||||
},
|
||||
want: configWithoutServices,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config, err := GenerateJSONConfig(tt.containers, "verification-response-body")
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, config.AppsRaw, 1, "Expected one http app")
|
||||
require.Contains(t, config.AppsRaw, "http", "Expected http app")
|
||||
|
||||
assert.JSONEq(t, tt.want, string(config.AppsRaw["http"]), "Generated Caddy app config doesn't match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newContainer(ip string, ports ...string) api.ServiceContainer {
|
||||
portsLabel := strings.Join(ports, ",")
|
||||
return api.ServiceContainer{Container: api.Container{InspectResponse: container.InspectResponse{
|
||||
ContainerJSONBase: &container.ContainerJSONBase{
|
||||
State: &container.State{
|
||||
Running: true,
|
||||
},
|
||||
},
|
||||
NetworkSettings: &container.NetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
docker.NetworkName: {
|
||||
IPAddress: ip,
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
api.LabelServicePorts: portsLabel,
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}
|
||||
|
||||
func newContainerWithoutNetwork(ports ...string) api.ServiceContainer {
|
||||
portsLabel := strings.Join(ports, ",")
|
||||
return api.ServiceContainer{Container: api.Container{InspectResponse: container.InspectResponse{
|
||||
ContainerJSONBase: &container.ContainerJSONBase{
|
||||
State: &container.State{
|
||||
Running: true,
|
||||
},
|
||||
},
|
||||
NetworkSettings: &container.NetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"other-network": {
|
||||
IPAddress: "172.17.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{
|
||||
api.LabelServicePorts: portsLabel,
|
||||
},
|
||||
},
|
||||
}}}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package caddyconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
)
|
||||
|
||||
// Server implements the gRPC Caddy service.
|
||||
@@ -22,18 +23,26 @@ func NewServer(service *Service) *Server {
|
||||
return &Server{service: service}
|
||||
}
|
||||
|
||||
// GetConfig retrieves the current Caddy configuration from the machine.
|
||||
// GetConfig retrieves the saved Caddy configuration and the latest reconciliation error 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) {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
if lastErr := s.service.LastReconciliationError(); lastErr != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "%v; last Caddy config load failed: %v", err, lastErr)
|
||||
}
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
}
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
recErr := ""
|
||||
if lastErr := s.service.LastReconciliationError(); lastErr != nil {
|
||||
recErr = lastErr.Error()
|
||||
}
|
||||
return &pb.GetCaddyConfigResponse{
|
||||
Caddyfile: caddyfile,
|
||||
ModifiedAt: timestamppb.New(modifiedAt),
|
||||
Caddyfile: caddyfile,
|
||||
ModifiedAt: timestamppb.New(modifiedAt),
|
||||
LastReconciliationError: recErr,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package caddyconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func TestServer_GetConfig(t *testing.T) {
|
||||
t.Run("returns saved Caddyfile and reconciliation error", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
caddyfile := "example.com { respond ok }\n"
|
||||
path := filepath.Join(dir, "Caddyfile")
|
||||
require.NoError(t, os.WriteFile(path, []byte(caddyfile), 0o640))
|
||||
info, err := os.Stat(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
service := NewService(dir)
|
||||
service.setReconciliationResult(errors.New("module not registered: caddy.storage.uncloud"))
|
||||
config, err := NewServer(service).GetConfig(context.Background(), &emptypb.Empty{})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, caddyfile, config.GetCaddyfile())
|
||||
assert.True(t, info.ModTime().Equal(config.GetModifiedAt().AsTime()))
|
||||
assert.Equal(t, "module not registered: caddy.storage.uncloud", config.GetLastReconciliationError())
|
||||
|
||||
service.setReconciliationResult(nil)
|
||||
config, err = NewServer(service).GetConfig(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, config.GetLastReconciliationError())
|
||||
})
|
||||
|
||||
t.Run("returns NotFound with reconciliation error when Caddyfile is absent", func(t *testing.T) {
|
||||
service := NewService(t.TempDir())
|
||||
service.setReconciliationResult(errors.New("module not registered: caddy.storage.uncloud"))
|
||||
|
||||
config, err := NewServer(service).GetConfig(context.Background(), &emptypb.Empty{})
|
||||
|
||||
assert.Nil(t, config)
|
||||
assert.Equal(t, codes.NotFound, status.Code(err))
|
||||
assert.ErrorContains(t, err, "Caddyfile")
|
||||
assert.ErrorContains(t, err, "last Caddy config load failed: module not registered: caddy.storage.uncloud")
|
||||
})
|
||||
|
||||
t.Run("returns NotFound without reconciliation error when Caddyfile is absent", func(t *testing.T) {
|
||||
service := NewService(t.TempDir())
|
||||
|
||||
config, err := NewServer(service).GetConfig(context.Background(), &emptypb.Empty{})
|
||||
|
||||
assert.Nil(t, config)
|
||||
assert.Equal(t, codes.NotFound, status.Code(err))
|
||||
assert.NotContains(t, err.Error(), "last Caddy config load failed")
|
||||
})
|
||||
}
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Service provides methods to interact with the Caddy configuration on the machine.
|
||||
type Service struct {
|
||||
configDir string
|
||||
configDir string
|
||||
mu sync.RWMutex
|
||||
lastReconciliationError error
|
||||
}
|
||||
|
||||
// NewService creates a new Service instance with the specified Caddy configuration directory.
|
||||
@@ -17,7 +20,8 @@ func NewService(configDir string) *Service {
|
||||
return &Service{configDir: configDir}
|
||||
}
|
||||
|
||||
// Caddyfile retrieves the current Caddy configuration (Caddyfile) from the machine's config directory.
|
||||
// Caddyfile retrieves the saved Caddyfile from the machine's config directory. The saved file may differ from the
|
||||
// running configuration if Caddy accepted a load but the subsequent write failed.
|
||||
func (s *Service) Caddyfile() (string, time.Time, error) {
|
||||
path := filepath.Join(s.configDir, "Caddyfile")
|
||||
content, err := os.ReadFile(path)
|
||||
@@ -33,3 +37,17 @@ func (s *Service) Caddyfile() (string, time.Time, error) {
|
||||
|
||||
return string(content), fileInfo.ModTime(), nil
|
||||
}
|
||||
|
||||
// LastReconciliationError reports the most recent unsuccessful controller attempt, if any. A successful attempt
|
||||
// clears it. An empty value does not prove that Caddy has loaded the saved Caddyfile.
|
||||
func (s *Service) LastReconciliationError() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.lastReconciliationError
|
||||
}
|
||||
|
||||
func (s *Service) setReconciliationResult(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.lastReconciliationError = err
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package caddyconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestService_LastReconciliationError(t *testing.T) {
|
||||
service := NewService(t.TempDir())
|
||||
assert.NoError(t, service.LastReconciliationError())
|
||||
|
||||
service.setReconciliationResult(errors.New("global Caddy config rejected"))
|
||||
assert.EqualError(t, service.LastReconciliationError(), "global Caddy config rejected")
|
||||
|
||||
service.setReconciliationResult(nil)
|
||||
assert.NoError(t, service.LastReconciliationError())
|
||||
}
|
||||
|
||||
func TestService_LastReconciliationErrorConcurrent(t *testing.T) {
|
||||
service := NewService(t.TempDir())
|
||||
var wg sync.WaitGroup
|
||||
for range 10 {
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
service.setReconciliationResult(errors.New("retry"))
|
||||
service.setReconciliationResult(nil)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for range 100 {
|
||||
_ = service.LastReconciliationError()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package caddystorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// ListCertificates lists issued certificates from this machine's Caddy storage replica.
|
||||
// Unreadable entries and invalid resource metadata are skipped. Chains and issuer data are returned as stored.
|
||||
func (s *Server) ListCertificates(ctx context.Context, _ *emptypb.Empty) (*pb.ListCertificatesResponse, error) {
|
||||
records, err := s.store.List(ctx, "certificates/", store.KeyspaceListOptions{KeysOnly: true})
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return nil, status.FromContextError(ctx.Err()).Err()
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "list Caddy certificates: %v", err)
|
||||
}
|
||||
|
||||
resp := &pb.ListCertificatesResponse{}
|
||||
for _, record := range records {
|
||||
if err = ctx.Err(); err != nil {
|
||||
return nil, status.FromContextError(err).Err()
|
||||
}
|
||||
if !isCertificateKey(record.Key) {
|
||||
continue
|
||||
}
|
||||
cert, err := s.loadIssuedCertificate(ctx, record.Key)
|
||||
if err != nil {
|
||||
slog.Warn("Failed to load issued certificate from Caddy storage.", "key", record.Key, "err", err)
|
||||
continue
|
||||
}
|
||||
resp.Certificates = append(resp.Certificates, cert)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// isCertificateKey recognizes CertMagic's certificates/<issuer>/<name>/<name>.crt layout.
|
||||
func isCertificateKey(key string) bool {
|
||||
parts := strings.Split(key, "/")
|
||||
return len(parts) == 4 && parts[0] == "certificates" && parts[3] == parts[2]+".crt"
|
||||
}
|
||||
|
||||
func (s *Server) loadIssuedCertificate(ctx context.Context, key string) (*pb.IssuedCertificate, error) {
|
||||
chain, err := s.store.Get(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metaKey := strings.TrimSuffix(key, ".crt") + ".json"
|
||||
meta, err := s.store.Get(ctx, metaKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resource certmagic.CertificateResource
|
||||
if err = json.Unmarshal(meta.Value, &resource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resource.SANs) == 0 || strings.TrimSpace(resource.SANs[0]) == "" {
|
||||
return nil, fmt.Errorf("certificate metadata has no SAN")
|
||||
}
|
||||
|
||||
return &pb.IssuedCertificate{
|
||||
San: resource.SANs[0],
|
||||
Chain: chain.Value,
|
||||
IssuerData: resource.IssuerData,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package caddystorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
func TestServerListCertificates(t *testing.T) {
|
||||
for _, sans := range [][]string{
|
||||
{"app.example.com"}, {"*.example.com"}, {"192.0.2.1"}, {"www.example.com", "example.com"},
|
||||
} {
|
||||
t.Run(sans[0], func(t *testing.T) {
|
||||
bundle, _, _ := certificateFixture(t, &x509.Certificate{
|
||||
DNSNames: []string{"example.com", "www.example.com"},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.0.2.1")},
|
||||
NotAfter: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
key := certmagic.StorageKeys.SiteCert("local", sans[0])
|
||||
storage := newCertificateStorage(t)
|
||||
raw := json.RawMessage(`{"unknown_issuer_field":true}`)
|
||||
storage.add(t, key, bundle, sans, raw)
|
||||
storage.keys = append(storage.keys, "certificates", "certificates/local", "certificates/local/app.crt",
|
||||
"certificates/local/app/wrong.crt", "certificates/local/app/app.crt/extra.crt",
|
||||
"pki/authorities/local/root.crt", "ocsp/certificate")
|
||||
|
||||
resp, err := NewServer(storage).ListCertificates(t.Context(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Certificates, 1)
|
||||
cert := resp.Certificates[0]
|
||||
assert.Equal(t, sans[0], cert.San)
|
||||
assert.Equal(t, bundle, cert.Chain, "PEM must be returned unchanged")
|
||||
assert.Equal(t, []byte(raw), cert.IssuerData)
|
||||
assert.Equal(t, []string{key, strings.TrimSuffix(key, ".crt") + ".json"}, storage.loads)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerListCertificates_PreservesIssuerData(t *testing.T) {
|
||||
bundle, _, _ := certificateFixture(t, &x509.Certificate{})
|
||||
for _, raw := range []string{
|
||||
"", "null", `{"url":"https://ca/cert/1","ca":"https://ca/directory"}`,
|
||||
`{"url":"https://ca/cert/1","ca":"https://ca/directory","renewal_info":{"_selectedTime":"invalid"}}`,
|
||||
`"provider-record"`, "[1,2,3]",
|
||||
} {
|
||||
t.Run(raw, func(t *testing.T) {
|
||||
storage := newCertificateStorage(t)
|
||||
storage.add(t, "certificates/custom/app/app.crt", bundle, []string{"app"}, json.RawMessage(raw))
|
||||
|
||||
resp, err := NewServer(storage).ListCertificates(t.Context(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Certificates, 1)
|
||||
assert.Equal(t, raw, string(resp.Certificates[0].IssuerData))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerListCertificates_OrderingAndDuplicates(t *testing.T) {
|
||||
bundle, _, _ := certificateFixture(t, &x509.Certificate{})
|
||||
storage := newCertificateStorage(t)
|
||||
storage.add(t, certmagic.StorageKeys.SiteCert("z-issuer", "a.example.com"), bundle, []string{"a.example.com"}, nil)
|
||||
storage.add(t, certmagic.StorageKeys.SiteCert("a-issuer", "z.example.com"), bundle, []string{"z.example.com"}, nil)
|
||||
storage.add(t, certmagic.StorageKeys.SiteCert("b-issuer", "z.example.com"), bundle, []string{"z.example.com"}, nil)
|
||||
|
||||
resp, err := NewServer(storage).ListCertificates(t.Context(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Certificates, 3)
|
||||
certs := resp.Certificates
|
||||
assert.Equal(t, []string{"z.example.com", "z.example.com", "a.example.com"},
|
||||
[]string{certs[0].San, certs[1].San, certs[2].San})
|
||||
for _, cert := range certs {
|
||||
assert.Equal(t, bundle, cert.Chain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerListCertificates_SkipsUnreadableEntries(t *testing.T) {
|
||||
bundle, _, _ := certificateFixture(t, &x509.Certificate{})
|
||||
const badKey = "certificates/local/bad/bad.crt"
|
||||
const metaKey = "certificates/local/bad/bad.json"
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
change func(*certificateStorageStub)
|
||||
}{
|
||||
{"missing chain", func(s *certificateStorageStub) { delete(s.values, badKey) }},
|
||||
{"chain read failure", func(s *certificateStorageStub) { s.loadErrors[badKey] = errors.New("offline") }},
|
||||
{"missing metadata", func(s *certificateStorageStub) { delete(s.values, metaKey) }},
|
||||
{"metadata read failure", func(s *certificateStorageStub) { s.loadErrors[metaKey] = errors.New("offline") }},
|
||||
{"invalid JSON", func(s *certificateStorageStub) { s.values[metaKey] = []byte("{") }},
|
||||
{"missing SAN", func(s *certificateStorageStub) { s.values[metaKey] = []byte(`{}`) }},
|
||||
{"empty first SAN", func(s *certificateStorageStub) { s.values[metaKey] = []byte(`{"sans":["","app"]}`) }},
|
||||
{"blank first SAN", func(s *certificateStorageStub) { s.values[metaKey] = []byte(`{"sans":[" "]}`) }},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
storage := newCertificateStorage(t)
|
||||
storage.add(t, badKey, bundle, []string{"bad"}, nil)
|
||||
storage.add(t, "certificates/local/good/good.crt", bundle, []string{"good"}, nil)
|
||||
tt.change(storage)
|
||||
|
||||
resp, err := NewServer(storage).ListCertificates(t.Context(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Certificates, 1)
|
||||
assert.Equal(t, "good", resp.Certificates[0].San)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("all entries unreadable", func(t *testing.T) {
|
||||
storage := newCertificateStorage(t)
|
||||
storage.keys = []string{badKey}
|
||||
resp, err := NewServer(storage).ListCertificates(t.Context(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, resp.Certificates)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServerListCertificates_ListErrors(t *testing.T) {
|
||||
for _, fail := range []bool{false, true} {
|
||||
storage := newCertificateStorage(t)
|
||||
if fail {
|
||||
storage.listError = errors.New("offline")
|
||||
}
|
||||
|
||||
resp, err := NewServer(storage).ListCertificates(t.Context(), &emptypb.Empty{})
|
||||
assert.Empty(t, storage.loads)
|
||||
if fail {
|
||||
assert.Nil(t, resp)
|
||||
assert.Equal(t, codes.Internal, status.Code(err))
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, resp.Certificates)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only reads are implemented. Unexpected writes panic through the nil embedded Keyspace.
|
||||
type certificateStorageStub struct {
|
||||
*store.Keyspace
|
||||
t *testing.T
|
||||
keys []string
|
||||
values map[string][]byte
|
||||
loads []string
|
||||
listCalls int
|
||||
listError error
|
||||
loadErrors map[string]error
|
||||
onLoad func(string)
|
||||
}
|
||||
|
||||
func newCertificateStorage(t *testing.T) *certificateStorageStub {
|
||||
return &certificateStorageStub{t: t, values: make(map[string][]byte), loadErrors: make(map[string]error)}
|
||||
}
|
||||
|
||||
func (s *certificateStorageStub) add(t *testing.T, key string, bundle []byte, sans []string, issuerData json.RawMessage) {
|
||||
t.Helper()
|
||||
metaKey := strings.TrimSuffix(key, ".crt") + ".json"
|
||||
keyKey := strings.TrimSuffix(key, ".crt") + ".key"
|
||||
meta, err := json.Marshal(certmagic.CertificateResource{SANs: sans, IssuerData: issuerData})
|
||||
require.NoError(t, err)
|
||||
s.keys = append(s.keys, key, metaKey, keyKey)
|
||||
s.values[key], s.values[metaKey], s.values[keyKey] = bundle, meta, []byte("must not read private keys")
|
||||
}
|
||||
|
||||
func (s *certificateStorageStub) List(ctx context.Context, prefix string, opts store.KeyspaceListOptions) ([]store.Record, error) {
|
||||
s.listCalls++
|
||||
assert.Equal(s.t, "certificates/", prefix)
|
||||
assert.True(s.t, opts.KeysOnly, "listing must not fetch private key values")
|
||||
var records []store.Record
|
||||
for _, key := range slices.Sorted(slices.Values(s.keys)) {
|
||||
records = append(records, store.Record{Key: key})
|
||||
}
|
||||
return records, s.listError
|
||||
}
|
||||
|
||||
func (s *certificateStorageStub) Get(ctx context.Context, key string) (store.Record, error) {
|
||||
require.False(s.t, strings.HasSuffix(key, ".key"), "private keys must never be loaded")
|
||||
s.loads = append(s.loads, key)
|
||||
if s.onLoad != nil {
|
||||
s.onLoad(key)
|
||||
}
|
||||
if err := s.loadErrors[key]; err != nil {
|
||||
return store.Record{}, err
|
||||
}
|
||||
value, ok := s.values[key]
|
||||
if !ok {
|
||||
return store.Record{}, store.ErrKeyNotFound
|
||||
}
|
||||
return store.Record{Key: key, Value: value}, nil
|
||||
}
|
||||
|
||||
func certificateFixture(t *testing.T, template *x509.Certificate) ([]byte, *x509.Certificate, *x509.Certificate) {
|
||||
t.Helper()
|
||||
publicKey, key, err := ed25519.GenerateKey(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
root := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "Test CA"}, IsCA: true,
|
||||
BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign,
|
||||
NotBefore: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
NotAfter: time.Date(2040, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
rootDER, err := x509.CreateCertificate(rand.Reader, root, root, publicKey, key)
|
||||
require.NoError(t, err)
|
||||
root, err = x509.ParseCertificate(rootDER)
|
||||
require.NoError(t, err)
|
||||
template.SerialNumber = big.NewInt(2)
|
||||
template.NotBefore = root.NotBefore
|
||||
if template.NotAfter.IsZero() {
|
||||
template.NotAfter = root.NotAfter
|
||||
}
|
||||
leafDER, err := x509.CreateCertificate(rand.Reader, template, root, publicKey, key)
|
||||
require.NoError(t, err)
|
||||
leaf, err := x509.ParseCertificate(leafDER)
|
||||
require.NoError(t, err)
|
||||
bundle := append(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafDER}),
|
||||
pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootDER})...)
|
||||
return bundle, leaf, root
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Package caddystorage implements the machine-local Caddy storage API.
|
||||
// See api/pb/caddy_storage.proto for the gRPC service definition.
|
||||
package caddystorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// Namespace identifies Caddy storage records in the cluster key-value store.
|
||||
const Namespace = "caddy_storage"
|
||||
|
||||
// Server implements the machine-local CaddyStorage gRPC service.
|
||||
type Server struct {
|
||||
pb.UnimplementedCaddyStorageServer
|
||||
store keyspace
|
||||
}
|
||||
|
||||
type keyspace interface {
|
||||
Get(context.Context, string) (store.Record, error)
|
||||
Put(context.Context, string, []byte) error
|
||||
Delete(context.Context, string, store.KeyspaceDeleteOptions) error
|
||||
List(context.Context, string, store.KeyspaceListOptions) ([]store.Record, error)
|
||||
}
|
||||
|
||||
func NewServer(store keyspace) *Server {
|
||||
return &Server{store: store}
|
||||
}
|
||||
|
||||
func (s *Server) Store(ctx context.Context, req *pb.StoreCaddyStorageRequest) (*emptypb.Empty, error) {
|
||||
if err := validateKey(req.Key); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
if err := s.store.Put(ctx, req.Key, req.Value); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "put value: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Load(ctx context.Context, req *pb.LoadCaddyStorageRequest) (*pb.LoadCaddyStorageResponse, error) {
|
||||
if err := validateKey(req.Key); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
record, err := s.store.Get(ctx, req.Key)
|
||||
if errors.Is(err, store.ErrKeyNotFound) {
|
||||
return nil, status.Errorf(codes.NotFound, "Caddy storage key %q not found", req.Key)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "get value: %v", err)
|
||||
}
|
||||
|
||||
return &pb.LoadCaddyStorageResponse{
|
||||
Value: record.Value,
|
||||
UpdatedAt: timestamppb.New(record.UpdatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Delete(ctx context.Context, req *pb.DeleteCaddyStorageRequest) (*emptypb.Empty, error) {
|
||||
if err := validateKey(req.Key); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
// Delete the key and all keys prefixed by it in case it's a directory.
|
||||
if err := errors.Join(
|
||||
s.store.Delete(ctx, req.Key, store.KeyspaceDeleteOptions{}),
|
||||
s.store.Delete(ctx, req.Key+"/", store.KeyspaceDeleteOptions{Prefix: true}),
|
||||
); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "delete key: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) List(ctx context.Context, req *pb.ListCaddyStorageRequest) (*pb.ListCaddyStorageResponse, error) {
|
||||
if req.Prefix != "" {
|
||||
if err := validateKey(req.Prefix); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
storagePrefix := req.Prefix
|
||||
if storagePrefix != "" {
|
||||
storagePrefix += "/"
|
||||
}
|
||||
records, err := s.store.List(ctx, storagePrefix, store.KeyspaceListOptions{KeysOnly: true})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "list keys: %v", err)
|
||||
}
|
||||
// No descendants can mean an existing terminal key or a missing path.
|
||||
// Check the exact key for non-root prefixes. An empty prefix lists the storage root.
|
||||
if len(records) == 0 && req.Prefix != "" {
|
||||
if _, err = s.store.Get(ctx, req.Prefix); errors.Is(err, store.ErrKeyNotFound) {
|
||||
return nil, status.Errorf(codes.NotFound, "Caddy storage prefix %q not found", req.Prefix)
|
||||
} else if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "get prefix value: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.ListCaddyStorageResponse{
|
||||
Keys: listKeys(records, req.Prefix, req.Recursive),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func listKeys(records []store.Record, prefix string, recursive bool) []string {
|
||||
keys := make(map[string]struct{})
|
||||
for _, record := range records {
|
||||
relative := record.Key
|
||||
if prefix != "" {
|
||||
relative = strings.TrimPrefix(record.Key, prefix+"/")
|
||||
}
|
||||
components := strings.Split(relative, "/")
|
||||
if !recursive {
|
||||
keys[path.Join(prefix, components[0])] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range components {
|
||||
keys[path.Join(prefix, path.Join(components[:i+1]...))] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return slices.Sorted(maps.Keys(keys))
|
||||
}
|
||||
|
||||
func (s *Server) Stat(ctx context.Context, req *pb.StatCaddyStorageRequest) (*pb.StatCaddyStorageResponse, error) {
|
||||
if err := validateKey(req.Key); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
record, err := s.store.Get(ctx, req.Key)
|
||||
if err == nil {
|
||||
return &pb.StatCaddyStorageResponse{
|
||||
Key: req.Key,
|
||||
UpdatedAt: timestamppb.New(record.UpdatedAt),
|
||||
Size: int64(len(record.Value)),
|
||||
IsTerminal: true,
|
||||
}, nil
|
||||
}
|
||||
if !errors.Is(err, store.ErrKeyNotFound) {
|
||||
return nil, status.Errorf(codes.Internal, "get value: %v", err)
|
||||
}
|
||||
|
||||
// A terminal key is not found. Check if there are any keys prefixed by the key to determine if it's a directory.
|
||||
records, err := s.store.List(ctx, req.Key+"/", store.KeyspaceListOptions{KeysOnly: true})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "list keys: %v", err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, status.Errorf(codes.NotFound, "Caddy storage key %q not found", req.Key)
|
||||
}
|
||||
|
||||
return &pb.StatCaddyStorageResponse{
|
||||
Key: req.Key,
|
||||
IsTerminal: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateKey(key string) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("key is empty")
|
||||
}
|
||||
if strings.HasPrefix(key, "/") || strings.HasSuffix(key, "/") {
|
||||
return fmt.Errorf("key %q must not have a leading or trailing slash", key)
|
||||
}
|
||||
if strings.Contains(key, "\\") {
|
||||
return fmt.Errorf("key %q must use forward slashes", key)
|
||||
}
|
||||
for component := range strings.SplitSeq(key, "/") {
|
||||
if component == "" || component == "." || component == ".." {
|
||||
return fmt.Errorf("key %q contains an invalid path component", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package caddystorage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestListKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
records []store.Record
|
||||
prefix string
|
||||
recursive bool
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "empty root",
|
||||
recursive: true,
|
||||
},
|
||||
{
|
||||
name: "root immediate children",
|
||||
records: []store.Record{
|
||||
{Key: "ocsp/example.com"},
|
||||
{Key: "certificates/issuer/example.com/example.com.key"},
|
||||
{Key: "certificates/issuer/example.com/example.com.crt"},
|
||||
},
|
||||
want: []string{
|
||||
"certificates",
|
||||
"ocsp",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "root recursive",
|
||||
records: []store.Record{
|
||||
{Key: "ocsp/example.com"},
|
||||
{Key: "certificates/issuer/example.com/example.com.key"},
|
||||
{Key: "certificates/issuer/example.com/example.com.crt"},
|
||||
},
|
||||
recursive: true,
|
||||
want: []string{
|
||||
"certificates",
|
||||
"certificates/issuer",
|
||||
"certificates/issuer/example.com",
|
||||
"certificates/issuer/example.com/example.com.crt",
|
||||
"certificates/issuer/example.com/example.com.key",
|
||||
"ocsp",
|
||||
"ocsp/example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested immediate children",
|
||||
records: []store.Record{
|
||||
{Key: "certificates/issuer/example.net/example.net.crt"},
|
||||
{Key: "certificates/issuer/example.com/example.com.key"},
|
||||
{Key: "certificates/issuer/example.com/example.com.crt"},
|
||||
},
|
||||
prefix: "certificates/issuer",
|
||||
want: []string{
|
||||
"certificates/issuer/example.com",
|
||||
"certificates/issuer/example.net",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested terminal children",
|
||||
records: []store.Record{
|
||||
{Key: "ocsp/example.net"},
|
||||
{Key: "ocsp/example.com"},
|
||||
},
|
||||
prefix: "ocsp",
|
||||
want: []string{
|
||||
"ocsp/example.com",
|
||||
"ocsp/example.net",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested recursive",
|
||||
records: []store.Record{
|
||||
{Key: "certificates/issuer/example.net/example.net.crt"},
|
||||
{Key: "certificates/issuer/example.com/example.com.key"},
|
||||
{Key: "certificates/issuer/example.com/example.com.crt"},
|
||||
},
|
||||
prefix: "certificates/issuer",
|
||||
recursive: true,
|
||||
want: []string{
|
||||
"certificates/issuer/example.com",
|
||||
"certificates/issuer/example.com/example.com.crt",
|
||||
"certificates/issuer/example.com/example.com.key",
|
||||
"certificates/issuer/example.net",
|
||||
"certificates/issuer/example.net/example.net.crt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := listKeys(tt.records, tt.prefix, tt.recursive)
|
||||
if len(tt.want) == 0 {
|
||||
require.Empty(t, got)
|
||||
return
|
||||
}
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
+51
-103
@@ -12,7 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/caddyconfig"
|
||||
"github.com/psviderski/uncloud/internal/machine/constants"
|
||||
"github.com/psviderski/uncloud/internal/machine/corromigrate"
|
||||
@@ -176,7 +176,7 @@ func (cc *clusterController) Run(ctx context.Context) error {
|
||||
// Start the network API server before waiting for the store sync so the machine is reachable on the mesh
|
||||
// during the sync and can serve requests that don't depend on the store.
|
||||
// Assume the management IP can't be changed when the network is running.
|
||||
apiAddr := net.JoinHostPort(cc.state.Network.ManagementIP.String(), strconv.Itoa(constants.MachineAPIPort))
|
||||
apiAddr := net.JoinHostPort(cc.state.Network.ManagementIP.String(), strconv.Itoa(constants.UncloudAPIPort))
|
||||
listener, err := net.Listen("tcp", apiAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen API port: %w", err)
|
||||
@@ -189,9 +189,10 @@ func (cc *clusterController) Run(ctx context.Context) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for the store database to sync to the minimum version before starting store-dependent components.
|
||||
// This prevents issues with using partially replicated data when the machine just joined the cluster,
|
||||
// e.g., an empty machine list causing WireGuard peer misconfiguration.
|
||||
// Wait for initial replication before starting store-dependent components, such as WireGuard peer reconciliation.
|
||||
// This prevents issues with using partially replicated data when the machine just joined the cluster, e.g.,
|
||||
// an empty machine list causing WireGuard peer misconfiguration.
|
||||
// This uses Store.WaitForVersion's replication-progress semantics, not an exact snapshot of the join-time data.
|
||||
if err = cc.waitStoreSync(ctx); err != nil {
|
||||
return fmt.Errorf("wait initial cluster store sync: %w", err)
|
||||
}
|
||||
@@ -366,120 +367,67 @@ func (cc *clusterController) handleEndpointChanges(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// waitStoreSync blocks until the local store version >= state.MinStoreVersion and any known gaps are synced.
|
||||
// No-op when MinStoreVersion is empty. Clears state.MinStoreVersion when reached.
|
||||
// waitStoreSync waits for initial replication using [store.Store.WaitForVersion].
|
||||
// No-op when MinStoreVersion is empty. Retries until cancellation and clears state.MinStoreVersion after success.
|
||||
func (cc *clusterController) waitStoreSync(ctx context.Context) error {
|
||||
target := cc.state.MinStoreVersion
|
||||
if len(target) == 0 {
|
||||
minVersion := cc.state.MinStoreVersion
|
||||
if len(minVersion) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
slog.Info("Waiting for the initial cluster store sync.", "actors", len(target))
|
||||
slog.Info("Waiting for the initial cluster store sync.", "min_version", minVersion)
|
||||
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
// Periodic warning to surface stuck NAT/connectivity issues without aborting.
|
||||
// Periodic deadlines surface stuck NAT/connectivity issues without aborting startup.
|
||||
warnInterval := 5 * time.Minute
|
||||
warnTimer := time.NewTimer(warnInterval)
|
||||
defer warnTimer.Stop()
|
||||
|
||||
var (
|
||||
lastLagging int
|
||||
lastErrLogTime time.Time
|
||||
)
|
||||
nextWarning := time.Now().Add(warnInterval)
|
||||
var lastErrLogTime time.Time
|
||||
|
||||
for {
|
||||
waitCtx, cancel := context.WithDeadline(ctx, nextWarning)
|
||||
err := cc.store.WaitForVersion(waitCtx, minVersion)
|
||||
cancel()
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, store.ErrInvalidStoreVersion) {
|
||||
return fmt.Errorf("wait for minimum cluster store version: %w", err)
|
||||
}
|
||||
|
||||
if !time.Now().Before(nextWarning) {
|
||||
slog.Warn("Cluster store sync still pending. Check connectivity to peers.")
|
||||
nextWarning = time.Now().Add(warnInterval)
|
||||
continue
|
||||
}
|
||||
|
||||
// Retry store errors, throttling logs to once every 5 seconds.
|
||||
if time.Since(lastErrLogTime) >= 5*time.Second {
|
||||
slog.Error("Failed to check cluster store replication, retrying.", "err", err)
|
||||
lastErrLogTime = time.Now()
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-warnTimer.C:
|
||||
local, err := cc.store.Version(ctx)
|
||||
if err == nil {
|
||||
slog.Error("Cluster store sync still pending. Check connectivity to peers.",
|
||||
"lagging_actors", laggingActors(local, target))
|
||||
} else {
|
||||
slog.Error("Cluster store sync still pending. Check connectivity to peers.", "err", err)
|
||||
}
|
||||
warnTimer.Reset(warnInterval)
|
||||
case <-ticker.C:
|
||||
local, err := cc.store.Version(ctx)
|
||||
if err != nil {
|
||||
// Throttle error logs to once every 5 seconds.
|
||||
if time.Since(lastErrLogTime) >= 5*time.Second {
|
||||
slog.Error("Failed to get the cluster store version, retrying.", "err", err)
|
||||
lastErrLogTime = time.Now()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
lagging := laggingActors(local, target)
|
||||
if len(lagging) == 0 {
|
||||
// Per-actor max doesn't imply contiguous apply: corrosion can buffer X:N before
|
||||
// X:N-1 arrives and track the gap separately. Wait for any remaining gaps to be synced.
|
||||
if err := cc.waitKnownMissingChanges(ctx); err != nil {
|
||||
return fmt.Errorf("wait for known missing changes: %w", err)
|
||||
}
|
||||
// If the context was cancelled mid-gap-fill, don't persist a "synced" state.
|
||||
if ctx.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear MinStoreVersion so next restart doesn't wait for sync.
|
||||
cc.state.mu.Lock()
|
||||
cc.state.MinStoreVersion = nil
|
||||
err = cc.state.Save()
|
||||
cc.state.mu.Unlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("save machine state after the initial cluster store sync: %w", err)
|
||||
}
|
||||
|
||||
slog.Info("Cluster store completed the initial sync.", "actors", len(target))
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(lagging) != lastLagging {
|
||||
slog.Info("Syncing cluster store.", "lagging_actors", lagging)
|
||||
lastLagging = len(lagging)
|
||||
}
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// laggingActors returns target actors whose local version is below the required value, as [have, need].
|
||||
func laggingActors(local, target map[string]int64) map[string][2]int64 {
|
||||
lagging := make(map[string][2]int64)
|
||||
for actor, need := range target {
|
||||
if have := local[actor]; have < need {
|
||||
lagging[actor] = [2]int64{have, need}
|
||||
}
|
||||
// Clear MinStoreVersion so the next restart doesn't wait for sync.
|
||||
cc.state.mu.Lock()
|
||||
cc.state.MinStoreVersion = nil
|
||||
err := cc.state.Save()
|
||||
if err != nil {
|
||||
cc.state.MinStoreVersion = minVersion
|
||||
}
|
||||
return lagging
|
||||
}
|
||||
|
||||
// waitKnownMissingChanges polls the store until all known missing changes have been synced.
|
||||
func (cc *clusterController) waitKnownMissingChanges(ctx context.Context) error {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
changes, err := cc.store.KnownMissingChanges(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("query known missing changes from cluster store: %w", err)
|
||||
}
|
||||
|
||||
if len(changes) == 0 {
|
||||
slog.Debug("All known missing changes have been synced to the cluster store.")
|
||||
return nil
|
||||
}
|
||||
|
||||
slog.Debug("Waiting for known missing changes to be synced to the cluster store.", "remaining",
|
||||
len(changes))
|
||||
}
|
||||
cc.state.mu.Unlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("save machine state after the initial cluster store sync: %w", err)
|
||||
}
|
||||
|
||||
slog.Info("Cluster store completed the initial sync.", "min_version", minVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runMachineSync keeps this machine's info in the cluster store in sync with the local state and the Docker engine
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/corrosion"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/network"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"github.com/psviderski/uncloud/internal/secret"
|
||||
|
||||
@@ -4,20 +4,24 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/psviderski/uncloud/internal/dns"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
undns "github.com/psviderski/uncloud/internal/dns"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// uncloudDNSKey is the key used to store the details of the reserved domain in the store.
|
||||
// uncloudDNSKey stores the cluster domain and, when reserved, its Uncloud DNS credentials.
|
||||
const uncloudDNSKey = "uncloud_dns"
|
||||
|
||||
type uncloudDNSDomain struct {
|
||||
// Endpoint is the API endpoint of the Uncloud DNS service where the domain is reserved.
|
||||
// An empty endpoint means the domain is managed externally.
|
||||
Endpoint string
|
||||
Name string
|
||||
// TODO: encrypt the token in the store.
|
||||
@@ -34,14 +38,14 @@ func (c *Cluster) ReserveDomain(ctx context.Context, req *pb.ReserveDomainReques
|
||||
}
|
||||
|
||||
if _, err := c.storedDomain(ctx); err == nil {
|
||||
return nil, status.Errorf(codes.AlreadyExists, "domain already reserved")
|
||||
return nil, status.Error(codes.AlreadyExists, "cluster domain already configured")
|
||||
} else {
|
||||
if s := status.Convert(err); s.Code() != codes.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dnsClient := dns.NewClient()
|
||||
dnsClient := undns.NewClient()
|
||||
name, token, err := dnsClient.ReserveDomain(req.Endpoint)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
@@ -60,7 +64,7 @@ func (c *Cluster) ReserveDomain(ctx context.Context, req *pb.ReserveDomainReques
|
||||
return nil, status.Errorf(codes.Internal, "store reserved domain: %v", err)
|
||||
}
|
||||
|
||||
return &pb.Domain{Name: name}, nil
|
||||
return &pb.Domain{Name: name, Reserved: proto.Bool(true)}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) GetDomain(ctx context.Context, _ *emptypb.Empty) (*pb.Domain, error) {
|
||||
@@ -73,7 +77,7 @@ func (c *Cluster) GetDomain(ctx context.Context, _ *emptypb.Empty) (*pb.Domain,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.Domain{Name: domain.Name}, nil
|
||||
return &pb.Domain{Name: domain.Name, Reserved: proto.Bool(domain.Endpoint != "")}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) storedDomain(ctx context.Context) (uncloudDNSDomain, error) {
|
||||
@@ -103,13 +107,63 @@ func (c *Cluster) ReleaseDomain(ctx context.Context, _ *emptypb.Empty) (*pb.Doma
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domain.Endpoint == "" {
|
||||
return nil, status.Error(codes.FailedPrecondition,
|
||||
"cluster domain is set manually, use 'uc dns set \"\"' to unset it")
|
||||
}
|
||||
|
||||
if err = c.store.Delete(ctx, uncloudDNSKey); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "delete domain from store: %v", err)
|
||||
}
|
||||
// TODO: implement and call Uncloud DNS endpoint to release/delete the domain.
|
||||
|
||||
return &pb.Domain{Name: domain.Name}, nil
|
||||
return &pb.Domain{Name: domain.Name, Reserved: proto.Bool(true)}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) SetDomain(ctx context.Context, req *pb.SetDomainRequest) (*emptypb.Empty, error) {
|
||||
if err := c.checkReady(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := req.GetName()
|
||||
if name != "" {
|
||||
if labels, ok := dns.IsDomainName(name); !ok || labels < 2 {
|
||||
return nil, status.Errorf(codes.InvalidArgument,
|
||||
"invalid cluster domain '%s': must be a valid domain name with at least two labels", name)
|
||||
}
|
||||
}
|
||||
name = strings.ToLower(strings.TrimSuffix(name, "."))
|
||||
|
||||
existing, err := c.storedDomain(ctx)
|
||||
if err != nil && status.Code(err) != codes.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
if name == "" {
|
||||
if err == nil {
|
||||
if existing.Endpoint != "" {
|
||||
return nil, status.Error(codes.FailedPrecondition,
|
||||
"cluster domain is reserved in Uncloud DNS, use 'uc dns release' to release it")
|
||||
}
|
||||
if err := c.store.Delete(ctx, uncloudDNSKey); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "unset cluster domain: %v", err)
|
||||
}
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
if err == nil {
|
||||
return nil, status.Error(codes.AlreadyExists, "cluster domain already configured")
|
||||
}
|
||||
|
||||
domain := uncloudDNSDomain{Name: name}
|
||||
domainJSON, err := json.Marshal(domain)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "marshal set domain for store: %v", err)
|
||||
}
|
||||
if err = c.store.Put(ctx, uncloudDNSKey, domainJSON); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "store set domain: %v", err)
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) CreateDomainRecords(
|
||||
@@ -124,12 +178,16 @@ func (c *Cluster) CreateDomainRecords(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dnsClient := dns.NewClient()
|
||||
recordsReq := make([]dns.RecordRequest, len(req.Records))
|
||||
if domain.Endpoint == "" {
|
||||
return nil, status.Error(codes.FailedPrecondition, "cluster domain is not reserved in Uncloud DNS")
|
||||
}
|
||||
|
||||
dnsClient := undns.NewClient()
|
||||
recordsReq := make([]undns.RecordRequest, len(req.Records))
|
||||
for i, r := range req.Records {
|
||||
recordsReq[i] = dns.RecordRequest{
|
||||
recordsReq[i] = undns.RecordRequest{
|
||||
Name: r.Name,
|
||||
Type: dns.RecordType(r.Type.String()),
|
||||
Type: undns.RecordType(r.Type.String()),
|
||||
Values: r.Values,
|
||||
}
|
||||
}
|
||||
@@ -149,9 +207,9 @@ func (c *Cluster) CreateDomainRecords(
|
||||
}
|
||||
|
||||
switch r.Type {
|
||||
case dns.RecordTypeA:
|
||||
case undns.RecordTypeA:
|
||||
resp.Records[i].Type = pb.DNSRecord_A
|
||||
case dns.RecordTypeAAAA:
|
||||
case undns.RecordTypeAAAA:
|
||||
resp.Records[i].Type = pb.DNSRecord_AAAA
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
// MachineAPIPort is the port for the Machine API service on the management WireGuard network.
|
||||
MachineAPIPort = 51000
|
||||
// UnregistryPort is the port for the embedded container registry listening on the machine IP.
|
||||
// UncloudAPIPort is the TCP port on which each machine serves the Uncloud API over the management WireGuard
|
||||
// network.
|
||||
UncloudAPIPort = 51000
|
||||
// UnregistryPort is the TCP port for the embedded container registry listening on the machine IP.
|
||||
UnregistryPort = 51500
|
||||
)
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/psviderski/uncloud/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/fs"
|
||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||
"github.com/psviderski/uncloud/internal/machine/store"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
systemd "github.com/coreos/go-systemd/daemon"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
@@ -22,6 +23,13 @@ const (
|
||||
Image = "ghcr.io/unlabs-dev/corrosion:2026.6.15"
|
||||
// ContainerName is the name of the managed Corrosion container.
|
||||
ContainerName = "uncloud-corrosion"
|
||||
|
||||
// systemdStartTimeoutExtension is the duration of each systemd service start timeout extension that prevents
|
||||
// a slow image pull from exceeding the start timeout (TimeoutStartSec). Extensions are a no-op if Corrosion
|
||||
// is not starting during a systemd service startup.
|
||||
systemdStartTimeoutExtension = 30 * time.Second
|
||||
// systemdStartTimeoutExtendMax caps the total duration the start timeout can be extended for.
|
||||
systemdStartTimeoutExtendMax = 5 * time.Minute
|
||||
)
|
||||
|
||||
type DockerService struct {
|
||||
@@ -175,8 +183,14 @@ func (s *DockerService) createAndStart(ctx context.Context) error {
|
||||
}
|
||||
defer respBody.Close()
|
||||
|
||||
// The pull on the first daemon start may take longer than the systemd service start timeout (TimeoutStartSec)
|
||||
// on a slow connection. Keep extending the timeout while the pull is in progress so systemd doesn't kill
|
||||
// the daemon before it reports readiness. This is a no-op if not running under systemd.
|
||||
stopExtending := extendSystemdStartTimeout(ctx)
|
||||
// Wait for pull to complete.
|
||||
if _, err := io.Copy(io.Discard, respBody); err != nil {
|
||||
_, err = io.Copy(io.Discard, respBody)
|
||||
stopExtending()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read pull response: %w", err)
|
||||
}
|
||||
slog.Info("Docker image pulled.", "image", s.Image, "duration", time.Since(start).String())
|
||||
@@ -192,3 +206,40 @@ func (s *DockerService) createAndStart(ctx context.Context) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extendSystemdStartTimeout periodically asks systemd to extend the service start timeout, for up to
|
||||
// systemdStartTimeoutExtendMax. The returned function stops the extensions. It is a no-op when the service
|
||||
// is not running under systemd (NOTIFY_SOCKET is not set).
|
||||
func extendSystemdStartTimeout(ctx context.Context) (stop context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
go func() {
|
||||
// Send extensions more frequently than they expire so a single missed tick doesn't time out the start.
|
||||
ticker := time.NewTicker(systemdStartTimeoutExtension / 3)
|
||||
defer ticker.Stop()
|
||||
deadline := time.Now().Add(systemdStartTimeoutExtendMax)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if time.Now().After(deadline) {
|
||||
slog.Warn("Stopped extending the systemd service start timeout: corrosion service is taking "+
|
||||
"too long to start.", "timeout", systemdStartTimeoutExtendMax)
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("EXTEND_TIMEOUT_USEC=%d", systemdStartTimeoutExtension.Microseconds())
|
||||
if _, err := systemd.SdNotify(false, msg); err != nil {
|
||||
slog.Warn("Failed to extend the systemd service start timeout when starting corrosion service.",
|
||||
"err", err)
|
||||
return
|
||||
}
|
||||
slog.Info("Extended systemd service start timeout while corrosion service is starting.",
|
||||
"duration", systemdStartTimeoutExtension)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return cancel
|
||||
}
|
||||
Loaded 100 of 228 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user