mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e35e69b4be | ||
|
|
11c2e641f1 | ||
|
|
bb97537df7 | ||
|
|
9f4b8179b5 | ||
|
|
f7b079c4f2 | ||
|
|
3648187219 | ||
|
|
93346301fb | ||
|
|
cc86c8660c | ||
|
|
2963436538 | ||
|
|
6e326277bb | ||
|
|
469d6fadcd | ||
|
|
5f214247ca | ||
|
|
0b8495e964 | ||
|
|
6068e24f4f | ||
|
|
7861d56dff | ||
|
|
7bc69b9a9e | ||
|
|
38800924ed | ||
|
|
cf7e333579 | ||
|
|
769f5e47c3 | ||
|
|
6e1165ec57 | ||
|
|
8d16da596c | ||
|
|
d6c06828b3 | ||
|
|
fe93d91894 | ||
|
|
3f70018e7c | ||
|
|
70e9385032 | ||
|
|
db3016fe89 | ||
|
|
c56385602e | ||
|
|
fbbf839258 | ||
|
|
eee28e2ead | ||
|
|
ef57b62625 | ||
|
|
e854bbb3e1 |
@@ -24,6 +24,8 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X github.com/psviderski/uncloud/internal/version.version={{ .Version }}
|
||||||
|
|
||||||
- id: uncloudd
|
- id: uncloudd
|
||||||
main: ./cmd/uncloudd
|
main: ./cmd/uncloudd
|
||||||
@@ -35,6 +37,8 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
|
ldflags:
|
||||||
|
- -s -w -X github.com/psviderski/uncloud/internal/version.version={{ .Version }}
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- id: uncloud
|
- id: uncloud
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ demo-reset:
|
|||||||
ssh root@5.223.45.199 "AUTO_CONFIRM=true sudo -E uncloud-uninstall"
|
ssh root@5.223.45.199 "AUTO_CONFIRM=true sudo -E uncloud-uninstall"
|
||||||
ssh spy@192.168.40.243 "AUTO_CONFIRM=true sudo -E uncloud-uninstall"
|
ssh spy@192.168.40.243 "AUTO_CONFIRM=true sudo -E uncloud-uninstall"
|
||||||
|
|
||||||
|
.PHONY: ucind-cluster
|
||||||
|
ucind-cluster:
|
||||||
|
go run ./cmd/ucind cluster rm && go run ./cmd/ucind cluster create -m 3
|
||||||
|
|
||||||
.PHONY: proto
|
.PHONY: proto
|
||||||
proto:
|
proto:
|
||||||
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Unlike traditional orchestrators, there's no central control plane and quorum to
|
|||||||
synchronized copy of the cluster state through peer-to-peer communication, keeping cluster operations functional even if
|
synchronized copy of the cluster state through peer-to-peer communication, keeping cluster operations functional even if
|
||||||
some machines go offline.
|
some machines go offline.
|
||||||
|
|
||||||
Uncloud aims to be the solution for developers who want the flexibility of self-hosted infrastructure without the
|
Uncloud is the solution for developers who want the flexibility of self-hosted infrastructure without the
|
||||||
operational complexity of Kubernetes.
|
operational complexity of Kubernetes.
|
||||||
|
|
||||||
## 🎬 Quick demo
|
## 🎬 Quick demo
|
||||||
@@ -28,13 +28,17 @@ Deploy a highly available web app with automatic HTTPS across multiple regions a
|
|||||||
|
|
||||||
* **Deploy anywhere**: Combine cloud VMs, dedicated servers, and bare metal into a unified computing environment —
|
* **Deploy anywhere**: Combine cloud VMs, dedicated servers, and bare metal into a unified computing environment —
|
||||||
regardless of location or provider.
|
regardless of location or provider.
|
||||||
|
* **Docker Compose**: Familiar [Docker Compose](https://compose-spec.io/) format for defining services and volumes. No
|
||||||
|
need to learn a new bespoke DSL.
|
||||||
|
* **Zero-downtime deployments**: Rolling updates without service interruption. Automatic rollback on failure is coming
|
||||||
|
soon.
|
||||||
|
* **Service discovery**: Built-in DNS server resolves service names to container IPs.
|
||||||
|
* **Persistent storage**: Run stateful services with Docker volumes managed across machines.
|
||||||
* **Zero-config private network**: Automatic WireGuard mesh with peer discovery and NAT traversal. Containers get unique
|
* **Zero-config private network**: Automatic WireGuard mesh with peer discovery and NAT traversal. Containers get unique
|
||||||
IPs for direct cross-machine communication.
|
IPs for direct cross-machine communication.
|
||||||
* **No control plane**: Fully decentralized design eliminates single points of failure and reduces operational overhead.
|
* **No control plane**: Fully decentralized design eliminates single points of failure and reduces operational overhead.
|
||||||
* **Imperative over declarative**: Favoring imperative operations over state reconciliation simplifies both the mental
|
* **Imperative over declarative**: Favoring imperative operations over state reconciliation simplifies both the mental
|
||||||
model and troubleshooting.
|
model and troubleshooting.
|
||||||
* **Zero-downtime deployments**: Rolling updates with health checks and automatic rollback on failure.
|
|
||||||
* **Service discovery**: Built-in DNS server resolves service names to container IPs.
|
|
||||||
* **Managed DNS**: Automatic DNS records `*.<id>.cluster.uncloud.run` for services with public access via managed
|
* **Managed DNS**: Automatic DNS records `*.<id>.cluster.uncloud.run` for services with public access via managed
|
||||||
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
[Uncloud DNS](https://github.com/psviderski/uncloud-dns) service.
|
||||||
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
* **Automatic HTTPS**: Built-in Caddy reverse proxy handles TLS certificate provisioning and renewal using Let's
|
||||||
@@ -42,14 +46,6 @@ Deploy a highly available web app with automatic HTTPS across multiple regions a
|
|||||||
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
* **Docker-like CLI**: Familiar commands for managing both infrastructure and applications.
|
||||||
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
* **Remote management**: Control your entire infrastructure through SSH access to any single machine in the cluster.
|
||||||
|
|
||||||
Coming soon:
|
|
||||||
|
|
||||||
* Infrastructure as Code using Docker Compose format
|
|
||||||
* Project isolation through environments/namespaces
|
|
||||||
* Persistent volumes and secrets management
|
|
||||||
* Monitoring and log aggregation
|
|
||||||
* Database deployment and management
|
|
||||||
|
|
||||||
Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
Here is a diagram of an Uncloud multi-provider cluster of 3 machines:
|
||||||
|
|
||||||

|

|
||||||
@@ -118,8 +114,10 @@ View the [user guide](docs/user_guide.md) for more information.
|
|||||||
|
|
||||||
## ⚙️ How it works
|
## ⚙️ How it works
|
||||||
|
|
||||||
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals. Here, let's peek
|
Check out the [design document](docs/design.md) to understand Uncloud's design philosophy and goals.
|
||||||
under the hood to see what happens when you run certain commands.
|
|
||||||
|
<details>
|
||||||
|
<summary>Here, let's peek under the hood to see what happens when you run certain commands.</summary>
|
||||||
|
|
||||||
**When you initialize a new cluster on a machine:**
|
**When you initialize a new cluster on a machine:**
|
||||||
|
|
||||||
@@ -254,6 +252,7 @@ Look ma, no control plane or master nodes to maintain! Just a simple overlay net
|
|||||||
sync that lets machines work together. Want to check on things or make changes? Connect to any machine either implicitly
|
sync that lets machines work together. Want to check on things or make changes? Connect to any machine either implicitly
|
||||||
using the CLI or directly over SSH. They all have the complete cluster state and can control everything. It's like each
|
using the CLI or directly over SSH. They all have the complete cluster state and can control everything. It's like each
|
||||||
machine is a full backup of your control plane.
|
machine is a full backup of your control plane.
|
||||||
|
</details>
|
||||||
|
|
||||||
## 🏗 Project status
|
## 🏗 Project status
|
||||||
|
|
||||||
@@ -270,7 +269,7 @@ I'd love your input! Here's how you can contribute:
|
|||||||
* [Subscribe](https://uncloud.run/#subscribe) to my newsletter to follow the progress, get early insights into new
|
* [Subscribe](https://uncloud.run/#subscribe) to my newsletter to follow the progress, get early insights into new
|
||||||
features, and be the first to know when it's ready for production use.
|
features, and be the first to know when it's ready for production use.
|
||||||
* Watch this repository for releases.
|
* Watch this repository for releases.
|
||||||
* Follow [@psviderski](https://github.com/psviderski) on GitHub.
|
* Follow [@psviderski](https://x.com/psviderski) on X/Twitter.
|
||||||
|
|
||||||
## ❤️ Contributors
|
## ❤️ Contributors
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,8 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, progressOut *streams.Out) error {
|
func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, progressOut *streams.Out) error {
|
||||||
if _, err := clusterClient.GetDomain(ctx); err != nil {
|
domain, err := clusterClient.GetDomain(ctx)
|
||||||
|
if err != nil {
|
||||||
if errors.Is(err, api.ErrNotFound) {
|
if errors.Is(err, api.ErrNotFound) {
|
||||||
fmt.Println("Skipping DNS records update as no cluster domain is reserved (see 'uc dns').")
|
fmt.Println("Skipping DNS records update as no cluster domain is reserved (see 'uc dns').")
|
||||||
return nil
|
return nil
|
||||||
@@ -164,7 +165,7 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
// TODO: split the method into two: one to get the records and one to update them to ask for update confirmation.
|
||||||
|
|
||||||
var records []*pb.DNSRecord
|
var records []*pb.DNSRecord
|
||||||
err := progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
records, err = clusterClient.CreateIngressRecords(ctx, client.CaddyServiceName)
|
records, err = clusterClient.CreateIngressRecords(ctx, client.CaddyServiceName)
|
||||||
return err
|
return err
|
||||||
@@ -172,18 +173,19 @@ func UpdateDomainRecords(ctx context.Context, clusterClient *client.Client, prog
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, client.ErrNoReachableMachines) {
|
if errors.Is(err, client.ErrNoReachableMachines) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("DNS records could not be updated as there are no internet-reachable machines running " +
|
fmt.Printf("DNS records for domain '%s' could not be updated as there are no internet-reachable "+
|
||||||
"caddy containers.")
|
"machines running caddy containers.\n", domain)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Possible solutions:")
|
fmt.Println("Possible solutions:")
|
||||||
fmt.Println("- Ensure your machines have public IP addresses")
|
fmt.Println("- Ensure your machines have public IP addresses")
|
||||||
fmt.Println("- Use --public-ip flag when adding machines to override the automatically detected IPs")
|
fmt.Println("- Use --public-ip flag when adding machines to override the automatically detected IPs")
|
||||||
fmt.Println("- Check firewall settings on your machines")
|
fmt.Println("- Check firewall settings on your machines")
|
||||||
fmt.Println("- Configure port forwarding if behind NAT")
|
fmt.Println("- Configure port forwarding if behind NAT")
|
||||||
fmt.Println("- Retry Caddy deployment after resolving connectivity issues with 'uc caddy deploy'")
|
fmt.Println("- Retry Caddy deployment with 'uc caddy deploy' after resolving connectivity issues")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Your services will not be accessible from the internet until at least one machine " +
|
fmt.Println("Your services won't be accessible from the internet until at least one machine " +
|
||||||
"becomes reachable.")
|
"becomes reachable. If you aren't planning to expose any services publicly, you can release " +
|
||||||
|
"the domain by running 'uc dns release'.")
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed to update DNS records pointing to caddy service: %w", err)
|
return fmt.Errorf("failed to update DNS records pointing to caddy service: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,5 @@ func buildContextOptions(contexts []string, current string) []huh.Option[string]
|
|||||||
options[i] = opt
|
options[i] = opt
|
||||||
}
|
}
|
||||||
|
|
||||||
options[1].Selected(true)
|
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-2
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
composecli "github.com/compose-spec/compose-go/v2/cli"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
@@ -17,6 +18,7 @@ import (
|
|||||||
|
|
||||||
type deployOptions struct {
|
type deployOptions struct {
|
||||||
files []string
|
files []string
|
||||||
|
profiles []string
|
||||||
services []string
|
services []string
|
||||||
|
|
||||||
context string
|
context string
|
||||||
@@ -40,7 +42,9 @@ func NewDeployCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringSliceVarP(&opts.files, "file", "f", nil,
|
cmd.Flags().StringSliceVarP(&opts.files, "file", "f", nil,
|
||||||
"One or more Compose files to deploy services from. (default compose-ports-long.yaml)")
|
"One or more Compose files to deploy services from. (default compose.yaml)")
|
||||||
|
cmd.Flags().StringSliceVarP(&opts.profiles, "profile", "p", nil,
|
||||||
|
"One or more Compose profiles to enable.")
|
||||||
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
|
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
|
||||||
"Name of the cluster context to deploy to (default is the current context)")
|
"Name of the cluster context to deploy to (default is the current context)")
|
||||||
// TODO: Consider adding a filter flag to specify which machines to deploy to but keep the rest running.
|
// TODO: Consider adding a filter flag to specify which machines to deploy to but keep the rest running.
|
||||||
@@ -49,9 +53,22 @@ func NewDeployCommand() *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// projectOpts returns the project options for the Compose file(s).
|
||||||
|
func projectOpts(opts deployOptions) []composecli.ProjectOptionsFn {
|
||||||
|
projectOpts := []composecli.ProjectOptionsFn{}
|
||||||
|
|
||||||
|
if len(opts.profiles) > 0 {
|
||||||
|
projectOpts = append(projectOpts, composecli.WithDefaultProfiles(opts.profiles...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectOpts
|
||||||
|
}
|
||||||
|
|
||||||
// runDeploy parses the Compose file(s) and deploys the services.
|
// runDeploy parses the Compose file(s) and deploys the services.
|
||||||
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
||||||
project, err := compose.LoadProject(ctx, opts.files)
|
projectOpts := projectOpts(opts)
|
||||||
|
|
||||||
|
project, err := compose.LoadProject(ctx, opts.files, projectOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("load compose file(s): %w", err)
|
return fmt.Errorf("load compose file(s): %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/fs"
|
"github.com/psviderski/uncloud/internal/fs"
|
||||||
|
"github.com/psviderski/uncloud/internal/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ func main() {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "uncloud",
|
Use: "uncloud",
|
||||||
Short: "A CLI tool for managing Uncloud resources such as clusters, machines, and services.",
|
Short: "A CLI tool for managing Uncloud resources such as clusters, machines, and services.",
|
||||||
|
Version: version.String(),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func confirm() (bool, error) {
|
|||||||
Negative("No").
|
Negative("No").
|
||||||
Value(&confirmed),
|
Value(&confirmed),
|
||||||
),
|
),
|
||||||
)
|
).WithAccessible(true)
|
||||||
if err := form.Run(); err != nil {
|
if err := form.Run(); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/daemon"
|
"github.com/psviderski/uncloud/internal/daemon"
|
||||||
"github.com/psviderski/uncloud/internal/log"
|
"github.com/psviderski/uncloud/internal/log"
|
||||||
"github.com/psviderski/uncloud/internal/machine"
|
"github.com/psviderski/uncloud/internal/machine"
|
||||||
|
"github.com/psviderski/uncloud/internal/version"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -22,6 +24,7 @@ func main() {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "uncloudd",
|
Use: "uncloudd",
|
||||||
Short: "Uncloud machine daemon.",
|
Short: "Uncloud machine daemon.",
|
||||||
|
Version: version.String(),
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ require (
|
|||||||
github.com/ipfs/go-log/v2 v2.5.1
|
github.com/ipfs/go-log/v2 v2.5.1
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/lmittmann/tint v1.0.5
|
github.com/lmittmann/tint v1.0.5
|
||||||
|
github.com/miekg/dns v1.1.65
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.0
|
github.com/opencontainers/image-spec v1.1.0
|
||||||
github.com/siderolabs/discovery-api v0.1.4
|
github.com/siderolabs/discovery-api v0.1.4
|
||||||
@@ -46,9 +47,9 @@ require (
|
|||||||
github.com/vishvananda/netlink v1.3.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.33.0
|
||||||
golang.org/x/net v0.34.0
|
golang.org/x/net v0.35.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.11.0
|
||||||
golang.org/x/sys v0.31.0
|
golang.org/x/sys v0.31.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
@@ -197,7 +198,6 @@ require (
|
|||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/mholt/acmez/v2 v2.0.3 // indirect
|
github.com/mholt/acmez/v2 v2.0.3 // indirect
|
||||||
github.com/miekg/dns v1.1.62 // indirect
|
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
github.com/mitchellh/cli v1.1.5 // indirect
|
github.com/mitchellh/cli v1.1.5 // indirect
|
||||||
@@ -303,11 +303,11 @@ require (
|
|||||||
go.uber.org/zap/exp v0.2.0 // indirect
|
go.uber.org/zap/exp v0.2.0 // indirect
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
|
||||||
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect
|
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.23.0 // indirect
|
||||||
golang.org/x/term v0.28.0 // indirect
|
golang.org/x/term v0.29.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/time v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.29.0 // indirect
|
golang.org/x/tools v0.30.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect
|
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
|
||||||
|
|||||||
@@ -721,8 +721,8 @@ github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
|
|||||||
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
||||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||||
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||||
@@ -1196,8 +1196,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -1214,8 +1214,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -1237,8 +1237,8 @@ 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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
@@ -1252,8 +1252,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -1310,8 +1310,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -1322,8 +1322,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -1345,8 +1345,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
|||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
+26
-3
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/fs"
|
"github.com/psviderski/uncloud/internal/fs"
|
||||||
@@ -16,8 +17,8 @@ import (
|
|||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/psviderski/uncloud/pkg/client/connector"
|
"github.com/psviderski/uncloud/pkg/client/connector"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
"github.com/charmbracelet/huh"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -194,6 +195,17 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check machine meets all necessary system requirements before proceeding.
|
||||||
|
checkResp, err := machineClient.CheckPrerequisites(ctx, &emptypb.Empty{})
|
||||||
|
// TODO(lhf): remove Unimplemented check when v0.9.0 is released.
|
||||||
|
if err != nil {
|
||||||
|
if status.Convert(err).Code() != codes.Unimplemented {
|
||||||
|
return nil, fmt.Errorf("check machine prerequisites: %w", err)
|
||||||
|
}
|
||||||
|
} else if !checkResp.Satisfied {
|
||||||
|
return nil, fmt.Errorf("machine prerequisites not satisfied: %s", checkResp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
req := &pb.InitClusterRequest{
|
req := &pb.InitClusterRequest{
|
||||||
MachineName: opts.MachineName,
|
MachineName: opts.MachineName,
|
||||||
Network: pb.NewIPPrefix(opts.Network),
|
Network: pb.NewIPPrefix(opts.Network),
|
||||||
@@ -275,6 +287,17 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check machine meets all necessary system requirements before proceeding.
|
||||||
|
checkResp, err := machineClient.CheckPrerequisites(ctx, &emptypb.Empty{})
|
||||||
|
// TODO(lhf): remove Unimplemented check when v0.9.0 is released.
|
||||||
|
if err != nil {
|
||||||
|
if status.Convert(err).Code() != codes.Unimplemented {
|
||||||
|
return nil, fmt.Errorf("check machine prerequisites: %w", err)
|
||||||
|
}
|
||||||
|
} else if !checkResp.Satisfied {
|
||||||
|
return nil, fmt.Errorf("machine prerequisites not satisfied: %s", checkResp.Error)
|
||||||
|
}
|
||||||
|
|
||||||
tokenResp, err := machineClient.Token(ctx, &emptypb.Empty{})
|
tokenResp, err := machineClient.Token(ctx, &emptypb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get remote machine token: %w", err)
|
return nil, fmt.Errorf("get remote machine token: %w", err)
|
||||||
@@ -403,7 +426,7 @@ func (cli *CLI) promptResetMachine() error {
|
|||||||
Negative("No").
|
Negative("No").
|
||||||
Value(&confirm),
|
Value(&confirm),
|
||||||
),
|
),
|
||||||
)
|
).WithAccessible(true)
|
||||||
if err := form.Run(); err != nil {
|
if err := form.Run(); err != nil {
|
||||||
return fmt.Errorf("prompt user to confirm: %w", err)
|
return fmt.Errorf("prompt user to confirm: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func Confirm() (bool, error) {
|
|||||||
Negative("No").
|
Negative("No").
|
||||||
Value(&confirmed),
|
Value(&confirmed),
|
||||||
),
|
),
|
||||||
)
|
).WithAccessible(true)
|
||||||
if err := form.Run(); err != nil {
|
if err := form.Run(); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -45,11 +45,19 @@ func (h *SlogTextHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *SlogTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
func (h *SlogTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
return h.TextHandler.WithAttrs(attrs)
|
return &SlogTextHandler{
|
||||||
|
TextHandler: h.TextHandler.WithAttrs(attrs).(*slog.TextHandler),
|
||||||
|
opts: h.opts,
|
||||||
|
w: h.w,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SlogTextHandler) WithGroup(name string) slog.Handler {
|
func (h *SlogTextHandler) WithGroup(name string) slog.Handler {
|
||||||
return h.TextHandler.WithGroup(name)
|
return &SlogTextHandler{
|
||||||
|
TextHandler: h.TextHandler.WithGroup(name).(*slog.TextHandler),
|
||||||
|
opts: h.opts,
|
||||||
|
w: h.w,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SlogTextHandler) Handle(ctx context.Context, r slog.Record) error {
|
func (h *SlogTextHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||||
|
|||||||
@@ -21,6 +21,63 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CheckPrerequisitesResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Overall status of the checks.
|
||||||
|
Satisfied bool `protobuf:"varint,1,opt,name=satisfied,proto3" json:"satisfied,omitempty"`
|
||||||
|
// Error message if not satisfied (empty if all checks pass).
|
||||||
|
Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CheckPrerequisitesResponse) Reset() {
|
||||||
|
*x = CheckPrerequisitesResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CheckPrerequisitesResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CheckPrerequisitesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CheckPrerequisitesResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CheckPrerequisitesResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CheckPrerequisitesResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CheckPrerequisitesResponse) GetSatisfied() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Satisfied
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CheckPrerequisitesResponse) GetError() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Error
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type MachineInfo struct {
|
type MachineInfo struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -35,7 +92,7 @@ type MachineInfo struct {
|
|||||||
func (x *MachineInfo) Reset() {
|
func (x *MachineInfo) Reset() {
|
||||||
*x = MachineInfo{}
|
*x = MachineInfo{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[0]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -48,7 +105,7 @@ func (x *MachineInfo) String() string {
|
|||||||
func (*MachineInfo) ProtoMessage() {}
|
func (*MachineInfo) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *MachineInfo) ProtoReflect() protoreflect.Message {
|
func (x *MachineInfo) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[0]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[1]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -61,7 +118,7 @@ func (x *MachineInfo) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use MachineInfo.ProtoReflect.Descriptor instead.
|
// Deprecated: Use MachineInfo.ProtoReflect.Descriptor instead.
|
||||||
func (*MachineInfo) Descriptor() ([]byte, []int) {
|
func (*MachineInfo) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{0}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *MachineInfo) GetId() string {
|
func (x *MachineInfo) GetId() string {
|
||||||
@@ -106,7 +163,7 @@ type NetworkConfig struct {
|
|||||||
func (x *NetworkConfig) Reset() {
|
func (x *NetworkConfig) Reset() {
|
||||||
*x = NetworkConfig{}
|
*x = NetworkConfig{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[1]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -119,7 +176,7 @@ func (x *NetworkConfig) String() string {
|
|||||||
func (*NetworkConfig) ProtoMessage() {}
|
func (*NetworkConfig) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *NetworkConfig) ProtoReflect() protoreflect.Message {
|
func (x *NetworkConfig) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[1]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -132,7 +189,7 @@ func (x *NetworkConfig) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use NetworkConfig.ProtoReflect.Descriptor instead.
|
// Deprecated: Use NetworkConfig.ProtoReflect.Descriptor instead.
|
||||||
func (*NetworkConfig) Descriptor() ([]byte, []int) {
|
func (*NetworkConfig) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{1}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NetworkConfig) GetSubnet() *IPPrefix {
|
func (x *NetworkConfig) GetSubnet() *IPPrefix {
|
||||||
@@ -180,7 +237,7 @@ type InitClusterRequest struct {
|
|||||||
func (x *InitClusterRequest) Reset() {
|
func (x *InitClusterRequest) Reset() {
|
||||||
*x = InitClusterRequest{}
|
*x = InitClusterRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -193,7 +250,7 @@ func (x *InitClusterRequest) String() string {
|
|||||||
func (*InitClusterRequest) ProtoMessage() {}
|
func (*InitClusterRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *InitClusterRequest) ProtoReflect() protoreflect.Message {
|
func (x *InitClusterRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[3]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -206,7 +263,7 @@ func (x *InitClusterRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use InitClusterRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use InitClusterRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*InitClusterRequest) Descriptor() ([]byte, []int) {
|
func (*InitClusterRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{2}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InitClusterRequest) GetMachineName() string {
|
func (x *InitClusterRequest) GetMachineName() string {
|
||||||
@@ -271,7 +328,7 @@ type InitClusterResponse struct {
|
|||||||
func (x *InitClusterResponse) Reset() {
|
func (x *InitClusterResponse) Reset() {
|
||||||
*x = InitClusterResponse{}
|
*x = InitClusterResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[3]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -284,7 +341,7 @@ func (x *InitClusterResponse) String() string {
|
|||||||
func (*InitClusterResponse) ProtoMessage() {}
|
func (*InitClusterResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *InitClusterResponse) ProtoReflect() protoreflect.Message {
|
func (x *InitClusterResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[3]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[4]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -297,7 +354,7 @@ func (x *InitClusterResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use InitClusterResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use InitClusterResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*InitClusterResponse) Descriptor() ([]byte, []int) {
|
func (*InitClusterResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{3}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InitClusterResponse) GetMachine() *MachineInfo {
|
func (x *InitClusterResponse) GetMachine() *MachineInfo {
|
||||||
@@ -319,7 +376,7 @@ type JoinClusterRequest struct {
|
|||||||
func (x *JoinClusterRequest) Reset() {
|
func (x *JoinClusterRequest) Reset() {
|
||||||
*x = JoinClusterRequest{}
|
*x = JoinClusterRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[4]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -332,7 +389,7 @@ func (x *JoinClusterRequest) String() string {
|
|||||||
func (*JoinClusterRequest) ProtoMessage() {}
|
func (*JoinClusterRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *JoinClusterRequest) ProtoReflect() protoreflect.Message {
|
func (x *JoinClusterRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[4]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[5]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -345,7 +402,7 @@ func (x *JoinClusterRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use JoinClusterRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use JoinClusterRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*JoinClusterRequest) Descriptor() ([]byte, []int) {
|
func (*JoinClusterRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{4}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *JoinClusterRequest) GetMachine() *MachineInfo {
|
func (x *JoinClusterRequest) GetMachine() *MachineInfo {
|
||||||
@@ -373,7 +430,7 @@ type TokenResponse struct {
|
|||||||
func (x *TokenResponse) Reset() {
|
func (x *TokenResponse) Reset() {
|
||||||
*x = TokenResponse{}
|
*x = TokenResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[5]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[6]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -386,7 +443,7 @@ func (x *TokenResponse) String() string {
|
|||||||
func (*TokenResponse) ProtoMessage() {}
|
func (*TokenResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *TokenResponse) ProtoReflect() protoreflect.Message {
|
func (x *TokenResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[5]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[6]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -399,7 +456,7 @@ func (x *TokenResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use TokenResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use TokenResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*TokenResponse) Descriptor() ([]byte, []int) {
|
func (*TokenResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{5}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *TokenResponse) GetToken() string {
|
func (x *TokenResponse) GetToken() string {
|
||||||
@@ -423,7 +480,7 @@ type Service struct {
|
|||||||
func (x *Service) Reset() {
|
func (x *Service) Reset() {
|
||||||
*x = Service{}
|
*x = Service{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[6]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[7]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -436,7 +493,7 @@ func (x *Service) String() string {
|
|||||||
func (*Service) ProtoMessage() {}
|
func (*Service) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Service) ProtoReflect() protoreflect.Message {
|
func (x *Service) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[6]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[7]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -449,7 +506,7 @@ func (x *Service) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Service.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Service.ProtoReflect.Descriptor instead.
|
||||||
func (*Service) Descriptor() ([]byte, []int) {
|
func (*Service) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{6}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{7}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Service) GetId() string {
|
func (x *Service) GetId() string {
|
||||||
@@ -491,7 +548,7 @@ type InspectServiceRequest struct {
|
|||||||
func (x *InspectServiceRequest) Reset() {
|
func (x *InspectServiceRequest) Reset() {
|
||||||
*x = InspectServiceRequest{}
|
*x = InspectServiceRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[7]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[8]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -504,7 +561,7 @@ func (x *InspectServiceRequest) String() string {
|
|||||||
func (*InspectServiceRequest) ProtoMessage() {}
|
func (*InspectServiceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *InspectServiceRequest) ProtoReflect() protoreflect.Message {
|
func (x *InspectServiceRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[7]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[8]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -517,7 +574,7 @@ func (x *InspectServiceRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use InspectServiceRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use InspectServiceRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*InspectServiceRequest) Descriptor() ([]byte, []int) {
|
func (*InspectServiceRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{7}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{8}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InspectServiceRequest) GetId() string {
|
func (x *InspectServiceRequest) GetId() string {
|
||||||
@@ -538,7 +595,7 @@ type InspectServiceResponse struct {
|
|||||||
func (x *InspectServiceResponse) Reset() {
|
func (x *InspectServiceResponse) Reset() {
|
||||||
*x = InspectServiceResponse{}
|
*x = InspectServiceResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[8]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[9]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -551,7 +608,7 @@ func (x *InspectServiceResponse) String() string {
|
|||||||
func (*InspectServiceResponse) ProtoMessage() {}
|
func (*InspectServiceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *InspectServiceResponse) ProtoReflect() protoreflect.Message {
|
func (x *InspectServiceResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[8]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[9]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -564,7 +621,7 @@ func (x *InspectServiceResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use InspectServiceResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use InspectServiceResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*InspectServiceResponse) Descriptor() ([]byte, []int) {
|
func (*InspectServiceResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{8}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *InspectServiceResponse) GetService() *Service {
|
func (x *InspectServiceResponse) GetService() *Service {
|
||||||
@@ -587,7 +644,7 @@ type Service_Container struct {
|
|||||||
func (x *Service_Container) Reset() {
|
func (x *Service_Container) Reset() {
|
||||||
*x = Service_Container{}
|
*x = Service_Container{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[9]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[10]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -600,7 +657,7 @@ func (x *Service_Container) String() string {
|
|||||||
func (*Service_Container) ProtoMessage() {}
|
func (*Service_Container) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Service_Container) ProtoReflect() protoreflect.Message {
|
func (x *Service_Container) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[9]
|
mi := &file_internal_machine_api_pb_machine_proto_msgTypes[10]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -613,7 +670,7 @@ func (x *Service_Container) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Service_Container.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Service_Container.ProtoReflect.Descriptor instead.
|
||||||
func (*Service_Container) Descriptor() ([]byte, []int) {
|
func (*Service_Container) Descriptor() ([]byte, []int) {
|
||||||
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{6, 0}
|
return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{7, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Service_Container) GetMachineId() string {
|
func (x *Service_Container) GetMachineId() string {
|
||||||
@@ -640,96 +697,106 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
|
|||||||
0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||||
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
||||||
0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||||
0x85, 0x01, 0x0a, 0x0b, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12,
|
0x50, 0x0a, 0x1a, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69,
|
||||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
|
0x73, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a,
|
||||||
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
0x09, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03,
|
0x52, 0x09, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x69, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65,
|
||||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f,
|
0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
|
||||||
0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
|
0x72, 0x22, 0x85, 0x01, 0x0a, 0x0b, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66,
|
||||||
0x6b, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x04,
|
0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
|
||||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x08, 0x70,
|
0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x22, 0xae, 0x01, 0x0a, 0x0d, 0x4e, 0x65, 0x74, 0x77,
|
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
|
||||||
0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x75, 0x62,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4e, 0x65, 0x74,
|
||||||
0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77,
|
||||||
0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x52, 0x06, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74,
|
0x6f, 0x72, 0x6b, 0x12, 0x24, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70,
|
||||||
0x12, 0x2c, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69,
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52,
|
||||||
0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50,
|
0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x22, 0xae, 0x01, 0x0a, 0x0d, 0x4e, 0x65,
|
||||||
0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x29,
|
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x06, 0x73,
|
||||||
0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
|
0x75, 0x62, 0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70,
|
||||||
0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x09,
|
0x69, 0x2e, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x52, 0x06, 0x73, 0x75, 0x62, 0x6e,
|
||||||
0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62,
|
0x65, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||||
0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70,
|
0x5f, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xc3, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x69,
|
0x49, 0x50, 0x52, 0x0c, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x70,
|
||||||
0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
0x12, 0x29, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20,
|
||||||
0x20, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01,
|
0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d,
|
0x52, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x70,
|
||||||
0x65, 0x12, 0x27, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01,
|
0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||||
0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69,
|
0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xc3, 0x01, 0x0a, 0x12, 0x49,
|
||||||
0x78, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x26, 0x0a, 0x09, 0x70, 0x75,
|
0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e,
|
0x74, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, 0x65,
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x48, 0x00, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e,
|
||||||
0x49, 0x70, 0x12, 0x26, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x5f,
|
0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02,
|
||||||
0x61, 0x75, 0x74, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x75,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x72, 0x65,
|
||||||
0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x41, 0x75, 0x74, 0x6f, 0x42, 0x12, 0x0a, 0x10, 0x70, 0x75,
|
0x66, 0x69, 0x78, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x26, 0x0a, 0x09,
|
||||||
0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x41,
|
0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73,
|
0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x48, 0x00, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x69, 0x63, 0x49, 0x70, 0x12, 0x26, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63,
|
0x70, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0c,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x70, 0x41, 0x75, 0x74, 0x6f, 0x42, 0x12, 0x0a, 0x10,
|
||||||
0x65, 0x22, 0x79, 0x0a, 0x12, 0x4a, 0x6f, 0x69, 0x6e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
|
0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
0x22, 0x41, 0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 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,
|
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,
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68,
|
||||||
0x69, 0x6e, 0x65, 0x12, 0x37, 0x0a, 0x0e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x63,
|
0x69, 0x6e, 0x65, 0x22, 0x79, 0x0a, 0x12, 0x4a, 0x6f, 0x69, 0x6e, 0x43, 0x6c, 0x75, 0x73, 0x74,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70,
|
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63,
|
||||||
0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d, 0x6f,
|
0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69,
|
||||||
0x74, 0x68, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x0d,
|
0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61,
|
||||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a,
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x37, 0x0a, 0x0e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x6d,
|
||||||
0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f,
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e,
|
||||||
0x6b, 0x65, 0x6e, 0x22, 0xc3, 0x01, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52,
|
||||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
|
0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x25,
|
||||||
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||||
0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61,
|
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xc3, 0x01, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||||
0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70,
|
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
|
||||||
0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69,
|
0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x6e, 0x65, 0x72, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x1a,
|
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20,
|
||||||
0x48, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a,
|
0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x36, 0x0a, 0x0a, 0x63, 0x6f, 0x6e,
|
||||||
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e,
|
||||||
0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63,
|
0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x74,
|
||||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09,
|
0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||||
0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, 0x27, 0x0a, 0x15, 0x49, 0x6e, 0x73,
|
0x73, 0x1a, 0x48, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1d,
|
||||||
0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a,
|
||||||
0x69, 0x64, 0x22, 0x40, 0x0a, 0x16, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72,
|
0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
|
||||||
0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x07,
|
0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, 0x27, 0x0a, 0x15, 0x49,
|
||||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e,
|
0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71,
|
||||||
0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x73, 0x65, 0x72,
|
0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x76, 0x69, 0x63, 0x65, 0x32, 0xc0, 0x02, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x52, 0x02, 0x69, 0x64, 0x22, 0x40, 0x0a, 0x16, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53,
|
||||||
0x12, 0x40, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12,
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26,
|
||||||
0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65,
|
0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49,
|
0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x73,
|
||||||
0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x32, 0x8f, 0x03, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65,
|
0x6e, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72, 0x65,
|
||||||
0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x43, 0x6c, 0x75, 0x73,
|
0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72,
|
||||||
0x74, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
|
0x65, 0x12, 0x40, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
|
||||||
|
0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74,
|
||||||
|
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
|
0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
|
0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x43, 0x6c, 0x75, 0x73, 0x74,
|
||||||
|
0x65, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x43, 0x6c, 0x75,
|
||||||
|
0x73, 0x74, 0x65, 0x72, 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,
|
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||||
0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52,
|
0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x2e, 0x67,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x70, 0x65,
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||||
0x63, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x70,
|
||||||
0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x49, 0x0a, 0x0e,
|
0x65, 0x63, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1a,
|
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x61, 0x70,
|
||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76,
|
0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x49, 0x0a,
|
||||||
0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69,
|
0x0e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
||||||
0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52,
|
0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75,
|
0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70,
|
||||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69,
|
0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||||
0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68,
|
||||||
0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62,
|
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b,
|
||||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
|
0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70,
|
||||||
|
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -744,48 +811,51 @@ func file_internal_machine_api_pb_machine_proto_rawDescGZIP() []byte {
|
|||||||
return file_internal_machine_api_pb_machine_proto_rawDescData
|
return file_internal_machine_api_pb_machine_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_internal_machine_api_pb_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
var file_internal_machine_api_pb_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||||
var file_internal_machine_api_pb_machine_proto_goTypes = []any{
|
var file_internal_machine_api_pb_machine_proto_goTypes = []any{
|
||||||
(*MachineInfo)(nil), // 0: api.MachineInfo
|
(*CheckPrerequisitesResponse)(nil), // 0: api.CheckPrerequisitesResponse
|
||||||
(*NetworkConfig)(nil), // 1: api.NetworkConfig
|
(*MachineInfo)(nil), // 1: api.MachineInfo
|
||||||
(*InitClusterRequest)(nil), // 2: api.InitClusterRequest
|
(*NetworkConfig)(nil), // 2: api.NetworkConfig
|
||||||
(*InitClusterResponse)(nil), // 3: api.InitClusterResponse
|
(*InitClusterRequest)(nil), // 3: api.InitClusterRequest
|
||||||
(*JoinClusterRequest)(nil), // 4: api.JoinClusterRequest
|
(*InitClusterResponse)(nil), // 4: api.InitClusterResponse
|
||||||
(*TokenResponse)(nil), // 5: api.TokenResponse
|
(*JoinClusterRequest)(nil), // 5: api.JoinClusterRequest
|
||||||
(*Service)(nil), // 6: api.Service
|
(*TokenResponse)(nil), // 6: api.TokenResponse
|
||||||
(*InspectServiceRequest)(nil), // 7: api.InspectServiceRequest
|
(*Service)(nil), // 7: api.Service
|
||||||
(*InspectServiceResponse)(nil), // 8: api.InspectServiceResponse
|
(*InspectServiceRequest)(nil), // 8: api.InspectServiceRequest
|
||||||
(*Service_Container)(nil), // 9: api.Service.Container
|
(*InspectServiceResponse)(nil), // 9: api.InspectServiceResponse
|
||||||
(*IP)(nil), // 10: api.IP
|
(*Service_Container)(nil), // 10: api.Service.Container
|
||||||
(*IPPrefix)(nil), // 11: api.IPPrefix
|
(*IP)(nil), // 11: api.IP
|
||||||
(*IPPort)(nil), // 12: api.IPPort
|
(*IPPrefix)(nil), // 12: api.IPPrefix
|
||||||
(*emptypb.Empty)(nil), // 13: google.protobuf.Empty
|
(*IPPort)(nil), // 13: api.IPPort
|
||||||
|
(*emptypb.Empty)(nil), // 14: google.protobuf.Empty
|
||||||
}
|
}
|
||||||
var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{
|
var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{
|
||||||
1, // 0: api.MachineInfo.network:type_name -> api.NetworkConfig
|
2, // 0: api.MachineInfo.network:type_name -> api.NetworkConfig
|
||||||
10, // 1: api.MachineInfo.public_ip:type_name -> api.IP
|
11, // 1: api.MachineInfo.public_ip:type_name -> api.IP
|
||||||
11, // 2: api.NetworkConfig.subnet:type_name -> api.IPPrefix
|
12, // 2: api.NetworkConfig.subnet:type_name -> api.IPPrefix
|
||||||
10, // 3: api.NetworkConfig.management_ip:type_name -> api.IP
|
11, // 3: api.NetworkConfig.management_ip:type_name -> api.IP
|
||||||
12, // 4: api.NetworkConfig.endpoints:type_name -> api.IPPort
|
13, // 4: api.NetworkConfig.endpoints:type_name -> api.IPPort
|
||||||
11, // 5: api.InitClusterRequest.network:type_name -> api.IPPrefix
|
12, // 5: api.InitClusterRequest.network:type_name -> api.IPPrefix
|
||||||
10, // 6: api.InitClusterRequest.public_ip:type_name -> api.IP
|
11, // 6: api.InitClusterRequest.public_ip:type_name -> api.IP
|
||||||
0, // 7: api.InitClusterResponse.machine:type_name -> api.MachineInfo
|
1, // 7: api.InitClusterResponse.machine:type_name -> api.MachineInfo
|
||||||
0, // 8: api.JoinClusterRequest.machine:type_name -> api.MachineInfo
|
1, // 8: api.JoinClusterRequest.machine:type_name -> api.MachineInfo
|
||||||
0, // 9: api.JoinClusterRequest.other_machines:type_name -> api.MachineInfo
|
1, // 9: api.JoinClusterRequest.other_machines:type_name -> api.MachineInfo
|
||||||
9, // 10: api.Service.containers:type_name -> api.Service.Container
|
10, // 10: api.Service.containers:type_name -> api.Service.Container
|
||||||
6, // 11: api.InspectServiceResponse.service:type_name -> api.Service
|
7, // 11: api.InspectServiceResponse.service:type_name -> api.Service
|
||||||
2, // 12: api.Machine.InitCluster:input_type -> api.InitClusterRequest
|
14, // 12: api.Machine.CheckPrerequisites:input_type -> google.protobuf.Empty
|
||||||
4, // 13: api.Machine.JoinCluster:input_type -> api.JoinClusterRequest
|
3, // 13: api.Machine.InitCluster:input_type -> api.InitClusterRequest
|
||||||
13, // 14: api.Machine.Token:input_type -> google.protobuf.Empty
|
5, // 14: api.Machine.JoinCluster:input_type -> api.JoinClusterRequest
|
||||||
13, // 15: api.Machine.Inspect:input_type -> google.protobuf.Empty
|
14, // 15: api.Machine.Token:input_type -> google.protobuf.Empty
|
||||||
7, // 16: api.Machine.InspectService:input_type -> api.InspectServiceRequest
|
14, // 16: api.Machine.Inspect:input_type -> google.protobuf.Empty
|
||||||
3, // 17: api.Machine.InitCluster:output_type -> api.InitClusterResponse
|
8, // 17: api.Machine.InspectService:input_type -> api.InspectServiceRequest
|
||||||
13, // 18: api.Machine.JoinCluster:output_type -> google.protobuf.Empty
|
0, // 18: api.Machine.CheckPrerequisites:output_type -> api.CheckPrerequisitesResponse
|
||||||
5, // 19: api.Machine.Token:output_type -> api.TokenResponse
|
4, // 19: api.Machine.InitCluster:output_type -> api.InitClusterResponse
|
||||||
0, // 20: api.Machine.Inspect:output_type -> api.MachineInfo
|
14, // 20: api.Machine.JoinCluster:output_type -> google.protobuf.Empty
|
||||||
8, // 21: api.Machine.InspectService:output_type -> api.InspectServiceResponse
|
6, // 21: api.Machine.Token:output_type -> api.TokenResponse
|
||||||
17, // [17:22] is the sub-list for method output_type
|
1, // 22: api.Machine.Inspect:output_type -> api.MachineInfo
|
||||||
12, // [12:17] is the sub-list for method input_type
|
9, // 23: api.Machine.InspectService:output_type -> api.InspectServiceResponse
|
||||||
|
18, // [18:24] is the sub-list for method output_type
|
||||||
|
12, // [12:18] is the sub-list for method input_type
|
||||||
12, // [12:12] is the sub-list for extension type_name
|
12, // [12:12] is the sub-list for extension type_name
|
||||||
12, // [12:12] is the sub-list for extension extendee
|
12, // [12:12] is the sub-list for extension extendee
|
||||||
0, // [0:12] is the sub-list for field type_name
|
0, // [0:12] is the sub-list for field type_name
|
||||||
@@ -799,7 +869,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
file_internal_machine_api_pb_common_proto_init()
|
file_internal_machine_api_pb_common_proto_init()
|
||||||
if !protoimpl.UnsafeEnabled {
|
if !protoimpl.UnsafeEnabled {
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*MachineInfo); i {
|
switch v := v.(*CheckPrerequisitesResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -811,7 +881,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[1].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*NetworkConfig); i {
|
switch v := v.(*MachineInfo); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -823,7 +893,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[2].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*InitClusterRequest); i {
|
switch v := v.(*NetworkConfig); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -835,7 +905,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[3].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*InitClusterResponse); i {
|
switch v := v.(*InitClusterRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -847,7 +917,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[4].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*JoinClusterRequest); i {
|
switch v := v.(*InitClusterResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -859,7 +929,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[5].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*TokenResponse); i {
|
switch v := v.(*JoinClusterRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -871,7 +941,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*Service); i {
|
switch v := v.(*TokenResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -883,7 +953,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*InspectServiceRequest); i {
|
switch v := v.(*Service); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -895,7 +965,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[8].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*InspectServiceResponse); i {
|
switch v := v.(*InspectServiceRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@@ -907,6 +977,18 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
file_internal_machine_api_pb_machine_proto_msgTypes[9].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*InspectServiceResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_internal_machine_api_pb_machine_proto_msgTypes[10].Exporter = func(v any, i int) any {
|
||||||
switch v := v.(*Service_Container); i {
|
switch v := v.(*Service_Container); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -919,7 +1001,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_internal_machine_api_pb_machine_proto_msgTypes[2].OneofWrappers = []any{
|
file_internal_machine_api_pb_machine_proto_msgTypes[3].OneofWrappers = []any{
|
||||||
(*InitClusterRequest_PublicIp)(nil),
|
(*InitClusterRequest_PublicIp)(nil),
|
||||||
(*InitClusterRequest_PublicIpAuto)(nil),
|
(*InitClusterRequest_PublicIpAuto)(nil),
|
||||||
}
|
}
|
||||||
@@ -929,7 +1011,7 @@ func file_internal_machine_api_pb_machine_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_internal_machine_api_pb_machine_proto_rawDesc,
|
RawDescriptor: file_internal_machine_api_pb_machine_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 10,
|
NumMessages: 11,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import "google/protobuf/empty.proto";
|
|||||||
import "internal/machine/api/pb/common.proto";
|
import "internal/machine/api/pb/common.proto";
|
||||||
|
|
||||||
service Machine {
|
service Machine {
|
||||||
|
// CheckPrerequisites verifies if the machine meets all necessary system requirements to participate in the cluster.
|
||||||
|
rpc CheckPrerequisites(google.protobuf.Empty) returns (CheckPrerequisitesResponse);
|
||||||
rpc InitCluster(InitClusterRequest) returns (InitClusterResponse);
|
rpc InitCluster(InitClusterRequest) returns (InitClusterResponse);
|
||||||
rpc JoinCluster(JoinClusterRequest) returns (google.protobuf.Empty);
|
rpc JoinCluster(JoinClusterRequest) returns (google.protobuf.Empty);
|
||||||
rpc Token(google.protobuf.Empty) returns (TokenResponse);
|
rpc Token(google.protobuf.Empty) returns (TokenResponse);
|
||||||
@@ -29,6 +31,13 @@ message NetworkConfig {
|
|||||||
bytes public_key = 4;
|
bytes public_key = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CheckPrerequisitesResponse {
|
||||||
|
// Overall status of the checks.
|
||||||
|
bool satisfied = 1;
|
||||||
|
// Error message if not satisfied (empty if all checks pass).
|
||||||
|
string error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message InitClusterRequest {
|
message InitClusterRequest {
|
||||||
string machineName = 1;
|
string machineName = 1;
|
||||||
IPPrefix network = 2;
|
IPPrefix network = 2;
|
||||||
|
|||||||
@@ -20,17 +20,19 @@ import (
|
|||||||
const _ = grpc.SupportPackageIsVersion9
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Machine_InitCluster_FullMethodName = "/api.Machine/InitCluster"
|
Machine_CheckPrerequisites_FullMethodName = "/api.Machine/CheckPrerequisites"
|
||||||
Machine_JoinCluster_FullMethodName = "/api.Machine/JoinCluster"
|
Machine_InitCluster_FullMethodName = "/api.Machine/InitCluster"
|
||||||
Machine_Token_FullMethodName = "/api.Machine/Token"
|
Machine_JoinCluster_FullMethodName = "/api.Machine/JoinCluster"
|
||||||
Machine_Inspect_FullMethodName = "/api.Machine/Inspect"
|
Machine_Token_FullMethodName = "/api.Machine/Token"
|
||||||
Machine_InspectService_FullMethodName = "/api.Machine/InspectService"
|
Machine_Inspect_FullMethodName = "/api.Machine/Inspect"
|
||||||
|
Machine_InspectService_FullMethodName = "/api.Machine/InspectService"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MachineClient is the client API for Machine service.
|
// MachineClient is the client API for Machine 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.
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
type MachineClient interface {
|
type MachineClient interface {
|
||||||
|
CheckPrerequisites(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CheckPrerequisitesResponse, error)
|
||||||
InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error)
|
InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error)
|
||||||
JoinCluster(ctx context.Context, in *JoinClusterRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
JoinCluster(ctx context.Context, in *JoinClusterRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
Token(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TokenResponse, error)
|
Token(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TokenResponse, error)
|
||||||
@@ -46,6 +48,16 @@ func NewMachineClient(cc grpc.ClientConnInterface) MachineClient {
|
|||||||
return &machineClient{cc}
|
return &machineClient{cc}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *machineClient) CheckPrerequisites(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CheckPrerequisitesResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(CheckPrerequisitesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, Machine_CheckPrerequisites_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *machineClient) InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error) {
|
func (c *machineClient) InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(InitClusterResponse)
|
out := new(InitClusterResponse)
|
||||||
@@ -100,6 +112,7 @@ func (c *machineClient) InspectService(ctx context.Context, in *InspectServiceRe
|
|||||||
// All implementations must embed UnimplementedMachineServer
|
// All implementations must embed UnimplementedMachineServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
type MachineServer interface {
|
type MachineServer interface {
|
||||||
|
CheckPrerequisites(context.Context, *emptypb.Empty) (*CheckPrerequisitesResponse, error)
|
||||||
InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error)
|
InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error)
|
||||||
JoinCluster(context.Context, *JoinClusterRequest) (*emptypb.Empty, error)
|
JoinCluster(context.Context, *JoinClusterRequest) (*emptypb.Empty, error)
|
||||||
Token(context.Context, *emptypb.Empty) (*TokenResponse, error)
|
Token(context.Context, *emptypb.Empty) (*TokenResponse, error)
|
||||||
@@ -115,6 +128,9 @@ type MachineServer interface {
|
|||||||
// pointer dereference when methods are called.
|
// pointer dereference when methods are called.
|
||||||
type UnimplementedMachineServer struct{}
|
type UnimplementedMachineServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedMachineServer) CheckPrerequisites(context.Context, *emptypb.Empty) (*CheckPrerequisitesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CheckPrerequisites not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedMachineServer) InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error) {
|
func (UnimplementedMachineServer) InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method InitCluster not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method InitCluster not implemented")
|
||||||
}
|
}
|
||||||
@@ -151,6 +167,24 @@ func RegisterMachineServer(s grpc.ServiceRegistrar, srv MachineServer) {
|
|||||||
s.RegisterService(&Machine_ServiceDesc, srv)
|
s.RegisterService(&Machine_ServiceDesc, srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Machine_CheckPrerequisites_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.(MachineServer).CheckPrerequisites(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Machine_CheckPrerequisites_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(MachineServer).CheckPrerequisites(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _Machine_InitCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Machine_InitCluster_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(InitClusterRequest)
|
in := new(InitClusterRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -248,6 +282,10 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
|||||||
ServiceName: "api.Machine",
|
ServiceName: "api.Machine",
|
||||||
HandlerType: (*MachineServer)(nil),
|
HandlerType: (*MachineServer)(nil),
|
||||||
Methods: []grpc.MethodDesc{
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "CheckPrerequisites",
|
||||||
|
Handler: _Machine_CheckPrerequisites_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "InitCluster",
|
MethodName: "InitCluster",
|
||||||
Handler: _Machine_InitCluster_Handler,
|
Handler: _Machine_InitCluster_Handler,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
||||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,34 +23,38 @@ func GenerateConfig(containers []api.ServiceContainer, verifyResponse string) (*
|
|||||||
httpHostUpstreams := make(map[string][]string)
|
httpHostUpstreams := make(map[string][]string)
|
||||||
httpsHostUpstreams := make(map[string][]string)
|
httpsHostUpstreams := make(map[string][]string)
|
||||||
for _, ctr := range containers {
|
for _, ctr := range containers {
|
||||||
logger := slog.With("container", ctr.ID)
|
if !ctr.Healthy() {
|
||||||
network, ok := ctr.NetworkSettings.Networks[docker.NetworkName]
|
|
||||||
if !ok {
|
|
||||||
// Container is not connected to the uncloud Docker network (could be host network).
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if network.IPAddress == "" {
|
|
||||||
logger.Error("Container has no IPv4 address.")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip := ctr.UncloudNetworkIP()
|
||||||
|
if !ip.IsValid() {
|
||||||
|
// Container is not connected to the uncloud Docker network (could be host network).
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log := slog.With("container", ctr.ID)
|
||||||
|
|
||||||
ports, err := ctr.ServicePorts()
|
ports, err := ctr.ServicePorts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to parse service ports for container.", "err", err)
|
log.Error("Failed to parse service ports for container.", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
|
if port.Mode != api.PortModeIngress {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch port.Protocol {
|
switch port.Protocol {
|
||||||
case api.ProtocolHTTP:
|
case api.ProtocolHTTP:
|
||||||
upstream := net.JoinHostPort(network.IPAddress, strconv.Itoa(int(port.ContainerPort)))
|
upstream := net.JoinHostPort(ip.String(), strconv.Itoa(int(port.ContainerPort)))
|
||||||
httpHostUpstreams[port.Hostname] = append(httpHostUpstreams[port.Hostname], upstream)
|
httpHostUpstreams[port.Hostname] = append(httpHostUpstreams[port.Hostname], upstream)
|
||||||
case api.ProtocolHTTPS:
|
case api.ProtocolHTTPS:
|
||||||
upstream := net.JoinHostPort(network.IPAddress, strconv.Itoa(int(port.ContainerPort)))
|
upstream := net.JoinHostPort(ip.String(), strconv.Itoa(int(port.ContainerPort)))
|
||||||
httpsHostUpstreams[port.Hostname] = append(httpsHostUpstreams[port.Hostname], upstream)
|
httpsHostUpstreams[port.Hostname] = append(httpsHostUpstreams[port.Hostname], upstream)
|
||||||
default:
|
default:
|
||||||
// TODO: implement L4 ingress routing for TCP and UDP.
|
// TODO: implement L4 ingress routing for TCP and UDP.
|
||||||
logger.Error("Unsupported protocol for ingress port.", "port", port)
|
log.Error("Unsupported protocol for ingress port.", "port", port)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateConfig(t *testing.T) {
|
func TestGenerateConfig(t *testing.T) {
|
||||||
|
configWithoutServices := `{
|
||||||
|
"servers": {
|
||||||
|
"http": {
|
||||||
|
"listen": [":80"],
|
||||||
|
"routes": [{
|
||||||
|
"match": [{"path": ["/.uncloud-verify"]}],
|
||||||
|
"handle": [{
|
||||||
|
"body": "verification-response-body",
|
||||||
|
"handler": "static_response",
|
||||||
|
"status_code": 200
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"listen": [":443"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
containers []api.ServiceContainer
|
containers []api.ServiceContainer
|
||||||
@@ -23,25 +42,8 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "empty containers",
|
name: "empty containers",
|
||||||
containers: []api.ServiceContainer{},
|
containers: []api.ServiceContainer{},
|
||||||
want: `{
|
want: configWithoutServices,
|
||||||
"servers": {
|
wantErr: false,
|
||||||
"http": {
|
|
||||||
"listen": [":80"],
|
|
||||||
"routes": [{
|
|
||||||
"match": [{"path": ["/.uncloud-verify"]}],
|
|
||||||
"handle": [{
|
|
||||||
"body": "verification-response-body",
|
|
||||||
"handler": "static_response",
|
|
||||||
"status_code": 200
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"listen": [":443"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -231,24 +233,7 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
containers: []api.ServiceContainer{
|
containers: []api.ServiceContainer{
|
||||||
newContainerWithoutNetwork("ignored.example.com:8080/http"),
|
newContainerWithoutNetwork("ignored.example.com:8080/http"),
|
||||||
},
|
},
|
||||||
want: `{
|
want: configWithoutServices,
|
||||||
"servers": {
|
|
||||||
"http": {
|
|
||||||
"listen": [":80"],
|
|
||||||
"routes": [{
|
|
||||||
"match": [{"path": ["/.uncloud-verify"]}],
|
|
||||||
"handle": [{
|
|
||||||
"body": "verification-response-body",
|
|
||||||
"handler": "static_response",
|
|
||||||
"status_code": 200
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"listen": [":443"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -256,45 +241,63 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
containers: []api.ServiceContainer{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2", "invalid-port"),
|
newContainer("10.210.0.2", "invalid-port"),
|
||||||
},
|
},
|
||||||
want: `{
|
want: configWithoutServices,
|
||||||
"servers": {
|
|
||||||
"http": {
|
|
||||||
"listen": [":80"],
|
|
||||||
"routes": [{
|
|
||||||
"match": [{"path": ["/.uncloud-verify"]}],
|
|
||||||
"handle": [{
|
|
||||||
"body": "verification-response-body",
|
|
||||||
"handler": "static_response",
|
|
||||||
"status_code": 200
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"listen": [":443"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "containers with unsupported protocols ignored",
|
name: "containers with unsupported protocols and host mode ignored",
|
||||||
containers: []api.ServiceContainer{
|
containers: []api.ServiceContainer{
|
||||||
newContainer("10.210.0.2", "5000/tcp"),
|
newContainer("10.210.0.2", "5000/tcp"),
|
||||||
newContainer("10.210.0.3", "5000/udp"),
|
newContainer("10.210.0.3", "5000/udp"),
|
||||||
newContainer("10.210.0.4", "80:8080/tcp@host"),
|
newContainer("10.210.0.4", "80:8080/tcp@host"),
|
||||||
},
|
},
|
||||||
|
want: configWithoutServices,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "restarting container ignored",
|
||||||
|
containers: []api.ServiceContainer{
|
||||||
|
newRestartingContainer("10.210.0.2", "app.example.com:8080/http"),
|
||||||
|
},
|
||||||
|
want: configWithoutServices,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stopped container ignored",
|
||||||
|
containers: []api.ServiceContainer{
|
||||||
|
newStoppedContainer("10.210.0.2", "app.example.com:8080/http"),
|
||||||
|
},
|
||||||
|
want: configWithoutServices,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mix of running, restarting, and stopped containers",
|
||||||
|
containers: []api.ServiceContainer{
|
||||||
|
newContainer("10.210.0.2", "app.example.com:8080/http"),
|
||||||
|
newRestartingContainer("10.210.0.3", "app.example.com:8080/http"),
|
||||||
|
newStoppedContainer("10.210.0.4", "app.example.com:8080/http"),
|
||||||
|
},
|
||||||
want: `{
|
want: `{
|
||||||
"servers": {
|
"servers": {
|
||||||
"http": {
|
"http": {
|
||||||
"listen": [":80"],
|
"listen": [":80"],
|
||||||
"routes": [{
|
"routes": [
|
||||||
"match": [{"path": ["/.uncloud-verify"]}],
|
{
|
||||||
"handle": [{
|
"match": [{"host": ["app.example.com"]}],
|
||||||
"body": "verification-response-body",
|
"handle": [{
|
||||||
"handler": "static_response",
|
"handler": "reverse_proxy",
|
||||||
"status_code": 200
|
"upstreams": [{"dial": "10.210.0.2:8080"}]
|
||||||
}]
|
}]
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
"match": [{"path": ["/.uncloud-verify"]}],
|
||||||
|
"handle": [{
|
||||||
|
"body": "verification-response-body",
|
||||||
|
"handler": "static_response",
|
||||||
|
"status_code": 200
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"https": {
|
"https": {
|
||||||
"listen": [":443"]
|
"listen": [":443"]
|
||||||
@@ -326,7 +329,11 @@ func TestGenerateConfig(t *testing.T) {
|
|||||||
func newContainer(ip string, ports ...string) api.ServiceContainer {
|
func newContainer(ip string, ports ...string) api.ServiceContainer {
|
||||||
portsLabel := strings.Join(ports, ",")
|
portsLabel := strings.Join(ports, ",")
|
||||||
return api.ServiceContainer{Container: api.Container{ContainerJSON: types.ContainerJSON{
|
return api.ServiceContainer{Container: api.Container{ContainerJSON: types.ContainerJSON{
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{},
|
ContainerJSONBase: &types.ContainerJSONBase{
|
||||||
|
State: &types.ContainerState{
|
||||||
|
Running: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
NetworkSettings: &types.NetworkSettings{
|
NetworkSettings: &types.NetworkSettings{
|
||||||
Networks: map[string]*network.EndpointSettings{
|
Networks: map[string]*network.EndpointSettings{
|
||||||
docker.NetworkName: {
|
docker.NetworkName: {
|
||||||
@@ -345,7 +352,11 @@ func newContainer(ip string, ports ...string) api.ServiceContainer {
|
|||||||
func newContainerWithoutNetwork(ports ...string) api.ServiceContainer {
|
func newContainerWithoutNetwork(ports ...string) api.ServiceContainer {
|
||||||
portsLabel := strings.Join(ports, ",")
|
portsLabel := strings.Join(ports, ",")
|
||||||
return api.ServiceContainer{Container: api.Container{ContainerJSON: types.ContainerJSON{
|
return api.ServiceContainer{Container: api.Container{ContainerJSON: types.ContainerJSON{
|
||||||
ContainerJSONBase: &types.ContainerJSONBase{},
|
ContainerJSONBase: &types.ContainerJSONBase{
|
||||||
|
State: &types.ContainerState{
|
||||||
|
Running: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
NetworkSettings: &types.NetworkSettings{
|
NetworkSettings: &types.NetworkSettings{
|
||||||
Networks: map[string]*network.EndpointSettings{
|
Networks: map[string]*network.EndpointSettings{
|
||||||
"other-network": {
|
"other-network": {
|
||||||
@@ -360,3 +371,15 @@ func newContainerWithoutNetwork(ports ...string) api.ServiceContainer {
|
|||||||
},
|
},
|
||||||
}}}
|
}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRestartingContainer(ip string, ports ...string) api.ServiceContainer {
|
||||||
|
ctr := newContainer(ip, ports...)
|
||||||
|
ctr.Container.State.Restarting = true
|
||||||
|
return ctr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStoppedContainer(ip string, ports ...string) api.ServiceContainer {
|
||||||
|
ctr := newContainer(ip, ports...)
|
||||||
|
ctr.Container.State.Running = false
|
||||||
|
return ctr
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/store"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClusterResolver implements Resolver by tracking containers in the cluster and resolving service names
|
||||||
|
// to their IP addresses.
|
||||||
|
type ClusterResolver struct {
|
||||||
|
store *store.Store
|
||||||
|
// serviceIPs maps service names to container IPs.
|
||||||
|
serviceIPs map[string][]netip.Addr
|
||||||
|
// mu protects the serviceIPs map.
|
||||||
|
mu sync.RWMutex
|
||||||
|
// lastUpdate tracks when records were last updated.
|
||||||
|
lastUpdate time.Time
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClusterResolver creates a new cluster resolver using the cluster store.
|
||||||
|
func NewClusterResolver(store *store.Store) *ClusterResolver {
|
||||||
|
return &ClusterResolver{
|
||||||
|
store: store,
|
||||||
|
serviceIPs: make(map[string][]netip.Addr),
|
||||||
|
log: slog.With("component", "dns-resolver"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts watching for container changes and updates DNS records accordingly.
|
||||||
|
func (r *ClusterResolver) Run(ctx context.Context) error {
|
||||||
|
containers, changes, err := r.store.SubscribeContainers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("subscribe to container changes: %w", err)
|
||||||
|
}
|
||||||
|
r.log.Info("Subscribed to container changes in the cluster to keep DNS records updated.")
|
||||||
|
|
||||||
|
// TODO: implement machine membership check using Corrossion Admin client to filter available containers.
|
||||||
|
r.updateServiceIPs(containers)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-changes:
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("containers subscription failed")
|
||||||
|
}
|
||||||
|
r.log.Debug("Cluster containers changed, updating DNS records.")
|
||||||
|
|
||||||
|
containers, err = r.store.ListContainers(ctx, store.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
r.log.Error("Failed to list containers.", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement machine membership check using Corrossion Admin client to filter available containers.
|
||||||
|
r.updateServiceIPs(containers)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateServiceIPs processes container records and updates the serviceIPs map.
|
||||||
|
func (r *ClusterResolver) updateServiceIPs(containers []store.ContainerRecord) {
|
||||||
|
newServiceIPs := make(map[string][]netip.Addr, len(r.serviceIPs))
|
||||||
|
|
||||||
|
containersCount := 0
|
||||||
|
for _, record := range containers {
|
||||||
|
if !record.Container.Healthy() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := record.Container.UncloudNetworkIP()
|
||||||
|
if !ip.IsValid() {
|
||||||
|
// Container is not connected to the uncloud Docker network (could be host network).
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr := api.ServiceContainer{Container: record.Container}
|
||||||
|
if ctr.ServiceID() == "" || ctr.ServiceName() == "" {
|
||||||
|
// Container is not part of a service, skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove normalisation after implementing service name validation:
|
||||||
|
//.https://github.com/psviderski/uncloud/issues/53
|
||||||
|
serviceName := strings.ToLower(ctr.ServiceName())
|
||||||
|
|
||||||
|
newServiceIPs[serviceName] = append(newServiceIPs[serviceName], ip)
|
||||||
|
// Also add the service ID as a valid lookup.
|
||||||
|
newServiceIPs[ctr.ServiceID()] = append(newServiceIPs[ctr.ServiceID()], ip)
|
||||||
|
containersCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the serviceIPs map atomically.
|
||||||
|
r.mu.Lock()
|
||||||
|
r.serviceIPs = newServiceIPs
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
r.log.Debug("DNS records updated.", "services", len(newServiceIPs)/2, "containers", containersCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve returns IP addresses of the service containers.
|
||||||
|
func (r *ClusterResolver) Resolve(serviceName string) []netip.Addr {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
ips, ok := r.serviceIPs[serviceName]
|
||||||
|
if !ok || len(ips) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a copy of the IPs slice to prevent modification of the original.
|
||||||
|
ipsCopy := make([]netip.Addr, len(ips))
|
||||||
|
copy(ipsCopy, ips)
|
||||||
|
|
||||||
|
return ipsCopy
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InternalDomain is the cluster internal domain for service discovery. All DNS queries ending with this suffix
|
||||||
|
// will be resolved using the internal DNS server.
|
||||||
|
InternalDomain = "internal."
|
||||||
|
// Port is the standard DNS port.
|
||||||
|
Port = 53
|
||||||
|
// maxConcurrentForwards is the maximum number of concurrent forwarded queries to upstream DNS servers.
|
||||||
|
// 1024 is the default used by the Docker internal DNS server.
|
||||||
|
maxConcurrentForwards = 1024
|
||||||
|
// forwardingTimeout is the timeout for forwarding a DNS query to an upstream server.
|
||||||
|
forwardingTimeout = 3 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resolver is an interface for resolving service names to IP addresses.
|
||||||
|
type Resolver interface {
|
||||||
|
// Resolve returns a list of IP addresses of the service containers.
|
||||||
|
// An empty list is returned if no service is found.
|
||||||
|
Resolve(serviceName string) []netip.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is an embedded internal DNS server for service discovery and forwarding external queries
|
||||||
|
// to upstream DNS servers.
|
||||||
|
type Server struct {
|
||||||
|
listenAddr netip.Addr
|
||||||
|
resolver Resolver
|
||||||
|
upstreamServers []netip.AddrPort
|
||||||
|
|
||||||
|
udpServer *dns.Server
|
||||||
|
tcpServer *dns.Server
|
||||||
|
inProgressReqs sync.WaitGroup
|
||||||
|
forwardSemaphore chan struct{}
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a new DNS server with the given configuration.
|
||||||
|
// If upstreams is nil, nameservers from /etc/resolv.conf will be used. An empty upstreams list means to only resolve
|
||||||
|
// internal DNS queries and not forward any external queries.
|
||||||
|
func NewServer(listenAddr netip.Addr, resolver Resolver, upstreams []netip.AddrPort) (*Server, error) {
|
||||||
|
if !listenAddr.IsValid() {
|
||||||
|
return nil, fmt.Errorf("invalid listen address: %s", listenAddr)
|
||||||
|
}
|
||||||
|
if resolver == nil {
|
||||||
|
return nil, fmt.Errorf("resolver must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load upstreams from /etc/resolv.conf and set fallback servers only if upstreams is not provided.
|
||||||
|
if upstreams == nil {
|
||||||
|
if nameservers, err := parseNameserversFromResolvConf(); err != nil {
|
||||||
|
slog.Warn("Failed to parse nameservers from /etc/resolv.conf.", "err", err)
|
||||||
|
} else {
|
||||||
|
for _, ns := range nameservers {
|
||||||
|
if ns.Compare(listenAddr) == 0 {
|
||||||
|
// Skip this internal DNS server address if it has been configured in /etc/resolv.conf
|
||||||
|
// to not create a forwarding loop.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
upstreams = append(upstreams, netip.AddrPortFrom(ns, Port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to common public DNS servers if no nameservers were found in /etc/resolv.conf.
|
||||||
|
if upstreams == nil {
|
||||||
|
upstreams = []netip.AddrPort{
|
||||||
|
netip.AddrPortFrom(netip.MustParseAddr("1.1.1.1"), Port), // Cloudflare DNS
|
||||||
|
netip.AddrPortFrom(netip.MustParseAddr("8.8.8.8"), Port), // Google DNS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
listenAddr: listenAddr,
|
||||||
|
resolver: resolver,
|
||||||
|
upstreamServers: upstreams,
|
||||||
|
forwardSemaphore: make(chan struct{}, maxConcurrentForwards),
|
||||||
|
log: slog.With("component", "dns-server"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAddr returns the address the DNS server is listening on.
|
||||||
|
func (s *Server) ListenAddr() netip.Addr {
|
||||||
|
return s.listenAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts the DNS server listening on both UDP and TCP ports. The server on TCP is not critical so it won't return
|
||||||
|
// an error if it fails to start. The server will run until the context is canceled or an error occurs.
|
||||||
|
func (s *Server) Run(ctx context.Context) error {
|
||||||
|
addr := net.JoinHostPort(s.listenAddr.String(), strconv.Itoa(Port))
|
||||||
|
s.udpServer = &dns.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Net: "udp",
|
||||||
|
Handler: dns.HandlerFunc(s.handleRequest),
|
||||||
|
}
|
||||||
|
s.tcpServer = &dns.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Net: "tcp",
|
||||||
|
Handler: dns.HandlerFunc(s.handleRequest),
|
||||||
|
}
|
||||||
|
|
||||||
|
errCh := make(chan error, 1) // Buffer size 1 is for UDP server error only.
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.log.Info("Starting DNS server on UDP port.", "addr", addr, "upstreams", s.upstreamServers)
|
||||||
|
if err := s.udpServer.ListenAndServe(); err != nil {
|
||||||
|
errCh <- fmt.Errorf("listen and serve on %s/udp: %w", addr, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.log.Info("Starting DNS server on TCP port.", "addr", addr, "upstreams", s.upstreamServers)
|
||||||
|
if err := s.tcpServer.ListenAndServe(); err != nil {
|
||||||
|
// TCP server is not critical, so log the error and continue.
|
||||||
|
slog.Warn("Failed to listen and serve DNS server on TCP port. "+
|
||||||
|
"TCP server is not critical and will be ignored.", "addr", addr, "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
// Stop the servers if the UDP one fails.
|
||||||
|
s.stop()
|
||||||
|
return err
|
||||||
|
case <-ctx.Done():
|
||||||
|
s.log.Info("Stopping DNS server.")
|
||||||
|
return s.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop gracefully shuts down the DNS server.
|
||||||
|
func (s *Server) stop() error {
|
||||||
|
var udpErr, tcpErr error
|
||||||
|
|
||||||
|
if s.udpServer != nil {
|
||||||
|
udpErr = s.udpServer.Shutdown()
|
||||||
|
}
|
||||||
|
if s.tcpServer != nil {
|
||||||
|
tcpErr = s.tcpServer.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all in-progress requests to finish.
|
||||||
|
s.inProgressReqs.Wait()
|
||||||
|
|
||||||
|
if udpErr != nil {
|
||||||
|
return fmt.Errorf("shutdown DNS server (UDP): %w", udpErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tcpErr != nil {
|
||||||
|
return fmt.Errorf("shutdown DNS server (TCP): %w", tcpErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRequest processes a DNS query and returns an appropriate response.
|
||||||
|
func (s *Server) handleRequest(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
|
s.inProgressReqs.Add(1)
|
||||||
|
defer s.inProgressReqs.Done()
|
||||||
|
|
||||||
|
if len(req.Question) == 0 {
|
||||||
|
resp := new(dns.Msg).SetRcode(req, dns.RcodeFormatError)
|
||||||
|
s.reply(w, req, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// While the original DNS RFCs allow multiple questions, in practice it never works. So handle only the first one.
|
||||||
|
q := req.Question[0]
|
||||||
|
log := s.log.With("name", q.Name, "type", dns.TypeToString[q.Qtype])
|
||||||
|
log.Debug("Received DNS query.")
|
||||||
|
|
||||||
|
if !dns.IsSubDomain(InternalDomain, dns.CanonicalName(q.Name)) {
|
||||||
|
log.Debug("Forwarding non-internal DNS query to upstream DNS servers.")
|
||||||
|
|
||||||
|
// Use the same transport for the forwarded request as the original request.
|
||||||
|
resp, err := s.forwardRequest(req, w.LocalAddr().Network())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to forward DNS query.", "err", err)
|
||||||
|
resp = new(dns.Msg).SetRcode(req, dns.RcodeServerFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.reply(w, req, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the query for the internal domain.
|
||||||
|
resp := new(dns.Msg).SetReply(req)
|
||||||
|
resp.Authoritative = true
|
||||||
|
resp.RecursionAvailable = true
|
||||||
|
|
||||||
|
switch q.Qtype {
|
||||||
|
case dns.TypeA:
|
||||||
|
records := s.handleAQuery(q.Name)
|
||||||
|
if len(records) > 0 {
|
||||||
|
log.Debug("Found A records for internal DNS query.", "count", len(records))
|
||||||
|
resp.Answer = append(resp.Answer, records...)
|
||||||
|
} else {
|
||||||
|
log.Debug("No records found for internal DNS query.")
|
||||||
|
resp.SetRcode(req, dns.RcodeNameError)
|
||||||
|
}
|
||||||
|
// TODO: Handle other query types (SRV, TXT, etc.) as needed.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate the response if it exceeds the maximum size for the transport protocol.
|
||||||
|
maxSize := dns.MinMsgSize
|
||||||
|
if w.LocalAddr().Network() == "tcp" {
|
||||||
|
maxSize = dns.MaxMsgSize
|
||||||
|
} else {
|
||||||
|
// Retrieve the UDP buffer size from the EDNS0 record if present.
|
||||||
|
if opt := req.IsEdns0(); opt != nil {
|
||||||
|
if udpSize := int(opt.UDPSize()); udpSize > maxSize {
|
||||||
|
maxSize = udpSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp.Truncate(maxSize)
|
||||||
|
|
||||||
|
s.reply(w, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) reply(w dns.ResponseWriter, req *dns.Msg, resp *dns.Msg) error {
|
||||||
|
if err := w.WriteMsg(resp); err != nil {
|
||||||
|
s.log.Error("Failed to write DNS response.", "err", err, "msg", resp)
|
||||||
|
// It may fail due to a malformed response message, e.g. exceeding the maximum size. In that case,
|
||||||
|
// attempt to send a server failure response instead so the client doesn't have to wait for a timeout.
|
||||||
|
if resp.Rcode != dns.RcodeServerFailure {
|
||||||
|
resp = new(dns.Msg).SetRcode(req, dns.RcodeServerFailure)
|
||||||
|
if err2 := w.WriteMsg(resp); err2 != nil {
|
||||||
|
s.log.Error("Failed to write DNS error response.", "err", err2, "msg", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// forwardRequest forwards a DNS query to system DNS servers
|
||||||
|
func (s *Server) forwardRequest(req *dns.Msg, proto string) (*dns.Msg, error) {
|
||||||
|
if len(s.upstreamServers) == 0 {
|
||||||
|
return nil, errors.New("no upstream DNS servers configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply concurrency control for forwarded queries.
|
||||||
|
select {
|
||||||
|
case s.forwardSemaphore <- struct{}{}:
|
||||||
|
defer func() {
|
||||||
|
<-s.forwardSemaphore
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("too many concurrent forwarded queries (max: %d)", maxConcurrentForwards)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create DNS client with timeout and same transport as original request
|
||||||
|
client := &dns.Client{
|
||||||
|
Net: proto,
|
||||||
|
Timeout: forwardingTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
for _, server := range s.upstreamServers {
|
||||||
|
resp, _, err := client.Exchange(req, server.String())
|
||||||
|
if err == nil {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
lastErr = err
|
||||||
|
s.log.Debug("Failed to forward DNS query to upstream server.", "server", server, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAQuery processes an A query for the internal domain and returns A records for the requested name.
|
||||||
|
// The internal domain suffix is already stripped from the name. An empty list is returned if no records are found.
|
||||||
|
func (s *Server) handleAQuery(name string) []dns.RR {
|
||||||
|
serviceName := trimInternalDomain(name)
|
||||||
|
ips := s.resolver.Resolve(serviceName)
|
||||||
|
if len(ips) == 0 {
|
||||||
|
s.log.Debug("Failed to resolve service name.", "service", serviceName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.log.Debug("Resolved service name.", "service", serviceName, "ips", ips)
|
||||||
|
|
||||||
|
if len(ips) > 1 {
|
||||||
|
// TODO: sort by proximity to the requesting container/machine. For now, just shuffle the IPs.
|
||||||
|
rand.Shuffle(len(ips), func(i, j int) {
|
||||||
|
ips[i], ips[j] = ips[j], ips[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create A records for each IP.
|
||||||
|
records := make([]dns.RR, 0, len(ips))
|
||||||
|
for _, ip := range ips {
|
||||||
|
records = append(records, &dns.A{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: name,
|
||||||
|
Rrtype: dns.TypeA,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
// TODO: should we increate the TTL to some reasonably small value like 5-30 seconds to allow
|
||||||
|
// at least some caching?
|
||||||
|
Ttl: 0,
|
||||||
|
},
|
||||||
|
A: net.ParseIP(ip.String()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseNameserversFromResolvConf parses the nameservers from /etc/resolv.conf.
|
||||||
|
func parseNameserversFromResolvConf() ([]netip.Addr, error) {
|
||||||
|
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var servers []netip.Addr
|
||||||
|
for _, server := range config.Servers {
|
||||||
|
if addr, err := netip.ParseAddr(server); err == nil {
|
||||||
|
servers = append(servers, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimInternalDomain(name string) string {
|
||||||
|
name = dns.CanonicalName(name)
|
||||||
|
if !dns.IsSubDomain(InternalDomain, name) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSuffix(name, "."+InternalDomain)
|
||||||
|
}
|
||||||
@@ -4,19 +4,19 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
dockercontainer "github.com/docker/docker/api/types/container"
|
dockercontainer "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/events"
|
"github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/psviderski/uncloud/internal/machine/store"
|
"github.com/psviderski/uncloud/internal/machine/store"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NetworkName = "uncloud"
|
NetworkName = "uncloud"
|
||||||
UserChain = "DOCKER-USER"
|
|
||||||
// EventsDebounceInterval defines how long to wait before processing the next Docker event. Multiple events
|
// EventsDebounceInterval defines how long to wait before processing the next Docker event. Multiple events
|
||||||
// occurring within this window will be processed together as a single event to prevent system overload.
|
// occurring within this window will be processed together as a single event to prevent system overload.
|
||||||
EventsDebounceInterval = 100 * time.Millisecond
|
EventsDebounceInterval = 100 * time.Millisecond
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnsureUncloudNetwork is a stub for darwin.
|
// EnsureUncloudNetwork is a stub for Darwin.
|
||||||
func (m *Manager) EnsureUncloudNetwork(ctx context.Context, subnet netip.Prefix) error {
|
func (m *Manager) EnsureUncloudNetwork(ctx context.Context, subnet netip.Prefix, dnsServer netip.Addr) error {
|
||||||
return fmt.Errorf("not supported on darwin")
|
return fmt.Errorf("not supported on Darwin")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,28 @@ package docker
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
dnetwork "github.com/docker/docker/api/types/network"
|
dnetwork "github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/libnetwork/iptables"
|
"github.com/docker/docker/libnetwork/iptables"
|
||||||
"log/slog"
|
"github.com/psviderski/uncloud/internal/machine/dns"
|
||||||
"net/netip"
|
"github.com/psviderski/uncloud/internal/machine/firewall"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnsureUncloudNetwork creates the Docker bridge network NetworkName with the provided machine subnet
|
// EnsureUncloudNetwork creates the Docker bridge network NetworkName with the provided machine subnet
|
||||||
// if it doesn't exist. If the network exists but has a different subnet, it removes and recreates the network.
|
// if it doesn't exist. If the network exists but has a different subnet, it removes and recreates the network.
|
||||||
// It also configures iptables to allow container access from the WireGuard network.
|
// It also configures iptables to allow container access from the WireGuard network.
|
||||||
func (m *Manager) EnsureUncloudNetwork(ctx context.Context, subnet netip.Prefix) error {
|
func (m *Manager) EnsureUncloudNetwork(ctx context.Context, subnet netip.Prefix, dnsServer netip.Addr) error {
|
||||||
// Ensure the Docker network 'uncloud' is created with the correct subnet.
|
// Ensure the Docker network 'uncloud' is created with the correct subnet.
|
||||||
needsCreation := false
|
needsCreation := false
|
||||||
nw, err := m.client.NetworkInspect(ctx, NetworkName, dnetwork.InspectOptions{})
|
nw, err := m.client.NetworkInspect(ctx, NetworkName, dnetwork.InspectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !client.IsErrNotFound(err) {
|
if !client.IsErrNotFound(err) {
|
||||||
return fmt.Errorf("inspect Docker network %q: %w", NetworkName, err)
|
return fmt.Errorf("inspect Docker network '%s': %w", NetworkName, err)
|
||||||
}
|
}
|
||||||
needsCreation = true
|
needsCreation = true
|
||||||
} else if nw.IPAM.Config[0].Subnet != subnet.String() {
|
} else if nw.IPAM.Config[0].Subnet != subnet.String() {
|
||||||
@@ -31,7 +35,7 @@ func (m *Manager) EnsureUncloudNetwork(ctx context.Context, subnet netip.Prefix)
|
|||||||
)
|
)
|
||||||
if err = m.client.NetworkRemove(ctx, NetworkName); err != nil {
|
if err = m.client.NetworkRemove(ctx, NetworkName); err != nil {
|
||||||
// It can still fail if the network is in use by a container. Leave it to the user to resolve the issue.
|
// It can still fail if the network is in use by a container. Leave it to the user to resolve the issue.
|
||||||
return fmt.Errorf("remove Docker network %q: %w", NetworkName, err)
|
return fmt.Errorf("remove Docker network '%s': %w", NetworkName, err)
|
||||||
}
|
}
|
||||||
needsCreation = true
|
needsCreation = true
|
||||||
}
|
}
|
||||||
@@ -50,12 +54,12 @@ func (m *Manager) EnsureUncloudNetwork(ctx context.Context, subnet netip.Prefix)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("create Docker network %q: %w", NetworkName, err)
|
return fmt.Errorf("create Docker network '%s': %w", NetworkName, err)
|
||||||
}
|
}
|
||||||
slog.Info("Docker network created.", "name", NetworkName, "subnet", subnet.String())
|
slog.Info("Docker network created.", "name", NetworkName, "subnet", subnet.String())
|
||||||
|
|
||||||
if nw, err = m.client.NetworkInspect(ctx, NetworkName, dnetwork.InspectOptions{}); err != nil {
|
if nw, err = m.client.NetworkInspect(ctx, NetworkName, dnetwork.InspectOptions{}); err != nil {
|
||||||
return fmt.Errorf("inspect Docker network %q: %w", NetworkName, err)
|
return fmt.Errorf("inspect Docker network '%s': %w", NetworkName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,10 +71,36 @@ func (m *Manager) EnsureUncloudNetwork(ctx context.Context, subnet netip.Prefix)
|
|||||||
// Bridge name doesn't seem to be documented but this is the source code where it is generated:
|
// Bridge name doesn't seem to be documented but this is the source code where it is generated:
|
||||||
// https://github.com/moby/moby/blob/v27.2.1/libnetwork/drivers/bridge/bridge_linux.go#L664
|
// https://github.com/moby/moby/blob/v27.2.1/libnetwork/drivers/bridge/bridge_linux.go#L664
|
||||||
bridgeName := "br-" + nw.ID[:12]
|
bridgeName := "br-" + nw.ID[:12]
|
||||||
|
|
||||||
|
if err = configureIptables(bridgeName, dnsServer); err != nil {
|
||||||
|
return fmt.Errorf("configure iptables for Docker network '%s': %w", NetworkName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureIptables configures iptables rules for the uncloud Docker network.
|
||||||
|
func configureIptables(bridgeName string, dnsServer netip.Addr) error {
|
||||||
ipt := iptables.GetIptable(iptables.IPv4)
|
ipt := iptables.GetIptable(iptables.IPv4)
|
||||||
rule := []string{"--in-interface", network.WireGuardInterfaceName, "--out-interface", bridgeName, "-j", "ACCEPT"}
|
// Allow traffic from other machines and their containers through the WG mesh to the Uncloud containers
|
||||||
if err = ipt.ProgramRule(iptables.Filter, UserChain, iptables.Insert, rule); err != nil {
|
// on the machine.
|
||||||
|
wgRule := []string{"--in-interface", network.WireGuardInterfaceName, "--out-interface", bridgeName, "-j", "ACCEPT"}
|
||||||
|
if err := ipt.ProgramRule(iptables.Filter, firewall.DockerUserChain, iptables.Insert, wgRule); err != nil {
|
||||||
return fmt.Errorf("insert iptables rule: %w", err)
|
return fmt.Errorf("insert iptables rule: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow DNS queries from Uncloud containers to the embedded DNS server.
|
||||||
|
for _, proto := range []string{"udp", "tcp"} {
|
||||||
|
dnsRule := []string{
|
||||||
|
"--in-interface", bridgeName,
|
||||||
|
"--dst", dnsServer.String(),
|
||||||
|
"--protocol", proto,
|
||||||
|
"--dport", strconv.Itoa(dns.Port),
|
||||||
|
"-j", "ACCEPT",
|
||||||
|
}
|
||||||
|
if err := ipt.ProgramRule(iptables.Filter, firewall.UncloudInputChain, iptables.Insert, dnsRule); err != nil {
|
||||||
|
return fmt.Errorf("insert iptables rule: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net/netip"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -30,6 +31,7 @@ import (
|
|||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/dns"
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -45,13 +47,17 @@ type Server struct {
|
|||||||
pb.UnimplementedDockerServer
|
pb.UnimplementedDockerServer
|
||||||
client *client.Client
|
client *client.Client
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
|
// internalDNSIP is a function that returns the IP address of the internal DNS server. It may return an empty
|
||||||
|
// address if the address is unknown (e.g. when the machine is not initialised yet).
|
||||||
|
internalDNSIP func() netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new Docker gRPC server with the provided Docker client.
|
// NewServer creates a new Docker gRPC server with the provided Docker client.
|
||||||
func NewServer(cli *client.Client, db *sqlx.DB) *Server {
|
func NewServer(cli *client.Client, db *sqlx.DB, internalDNSIP func() netip.Addr) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
client: cli,
|
client: cli,
|
||||||
db: db,
|
db: db,
|
||||||
|
internalDNSIP: internalDNSIP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,6 +511,16 @@ func (s *Server) CreateServiceContainer(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure the container to use the internal DNS server if it's available.
|
||||||
|
dnsIP := s.internalDNSIP()
|
||||||
|
if dnsIP.IsValid() {
|
||||||
|
hostConfig.DNS = []string{dnsIP.String()}
|
||||||
|
// Optimize DNS resolution for service discovery by appending the search domain to names without a dot.
|
||||||
|
// For example, the first attempt for "my-service" will be "my-service.internal".
|
||||||
|
hostConfig.DNSOptions = []string{"ndots:1"}
|
||||||
|
hostConfig.DNSSearch = []string{dns.InternalDomain}
|
||||||
|
}
|
||||||
|
|
||||||
if spec.Container.LogDriver != nil {
|
if spec.Container.LogDriver != nil {
|
||||||
hostConfig.LogConfig = container.LogConfig{
|
hostConfig.LogConfig = container.LogConfig{
|
||||||
Type: spec.Container.LogDriver.Name,
|
Type: spec.Container.LogDriver.Name,
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ConfigureIptablesChains is a stub for Darwin.
|
||||||
|
func ConfigureIptablesChains() error {
|
||||||
|
return fmt.Errorf("not supported on Darwin")
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/libnetwork/iptables"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DockerUserChain = "DOCKER-USER"
|
||||||
|
UncloudInputChain = "UNCLOUD-INPUT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigureIptablesChains sets up custom iptables chains and initial firewall rules for Uncloud networking.
|
||||||
|
func ConfigureIptablesChains() error {
|
||||||
|
// Ensure iptables UNCLOUD-INPUT chain with a RETURN rule exists. All existing rules are flushed.
|
||||||
|
ipt := iptables.GetIptable(iptables.IPv4)
|
||||||
|
if _, err := ipt.NewChain(UncloudInputChain, iptables.Filter); err != nil {
|
||||||
|
return fmt.Errorf("create iptables chain '%s': %w", UncloudInputChain, err)
|
||||||
|
}
|
||||||
|
if err := ipt.RawCombinedOutput("-t", string(iptables.Filter), "-F", UncloudInputChain); err != nil {
|
||||||
|
return fmt.Errorf("flush iptables chain '%s': %w", UncloudInputChain, err)
|
||||||
|
}
|
||||||
|
if err := ipt.AddReturnRule(UncloudInputChain); err != nil {
|
||||||
|
return fmt.Errorf("add the RETURN rule for iptables chain '%s': %w", UncloudInputChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the main iptables INPUT chain has a jump rule to the UNCLOUD-INPUT chain before any DROP/REJECT rules.
|
||||||
|
jumpRule := []string{"-m", "comment", "--comment", "Uncloud-managed", "-j", UncloudInputChain}
|
||||||
|
if !ipt.Exists(iptables.Filter, "INPUT", jumpRule...) {
|
||||||
|
// Look for the first DROP/REJECT rule in the INPUT chain.
|
||||||
|
out, err := ipt.Raw("-t", string(iptables.Filter), "-L", "INPUT", "--line-numbers")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get iptables rules for chain '%s': %w", UncloudInputChain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstRejectRuleNum := 0
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fields[1] == "DROP" || fields[1] == "REJECT" {
|
||||||
|
if ruleNum, err := strconv.Atoi(fields[0]); err == nil {
|
||||||
|
firstRejectRuleNum = ruleNum
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var addJumpRule []string
|
||||||
|
if firstRejectRuleNum > 0 {
|
||||||
|
addJumpRule = append([]string{"-t", string(iptables.Filter), "-I", "INPUT", strconv.Itoa(firstRejectRuleNum)},
|
||||||
|
jumpRule...)
|
||||||
|
} else {
|
||||||
|
addJumpRule = append([]string{"-t", string(iptables.Filter), "-A", "INPUT"}, jumpRule...)
|
||||||
|
}
|
||||||
|
if err = ipt.RawCombinedOutput(addJumpRule...); err != nil {
|
||||||
|
return fmt.Errorf("add iptables rule '%s': %w", strings.Join(addJumpRule, " "), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow WireGuard traffic to the machine.
|
||||||
|
acceptWireGuardRule := []string{"-p", "udp", "--dport", strconv.Itoa(network.WireGuardPort), "-j", "ACCEPT"}
|
||||||
|
err := ipt.ProgramRule(iptables.Filter, UncloudInputChain, iptables.Insert, acceptWireGuardRule)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("insert iptables rule '%s': %w", strings.Join(acceptWireGuardRule, " "), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/psviderski/uncloud/internal/machine/caddyfile"
|
"github.com/psviderski/uncloud/internal/machine/caddyfile"
|
||||||
"github.com/psviderski/uncloud/internal/machine/cluster"
|
"github.com/psviderski/uncloud/internal/machine/cluster"
|
||||||
"github.com/psviderski/uncloud/internal/machine/corroservice"
|
"github.com/psviderski/uncloud/internal/machine/corroservice"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/dns"
|
||||||
machinedocker "github.com/psviderski/uncloud/internal/machine/docker"
|
machinedocker "github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/psviderski/uncloud/internal/machine/store"
|
"github.com/psviderski/uncloud/internal/machine/store"
|
||||||
@@ -62,6 +63,8 @@ type Config struct {
|
|||||||
// CaddyConfigPath specifies where the machine generates the Caddy reverse proxy configuration file for routing
|
// CaddyConfigPath specifies where the machine generates the Caddy reverse proxy configuration file for routing
|
||||||
// external traffic to service containers across the internal network. Default is DataDir/caddy/caddy.json.
|
// external traffic to service containers across the internal network. Default is DataDir/caddy/caddy.json.
|
||||||
CaddyConfigPath string
|
CaddyConfigPath string
|
||||||
|
// DNSUpstreams specifies the upstream DNS servers for the embedded internal DNS server.
|
||||||
|
DNSUpstreams []netip.AddrPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults returns a new Config with default values set where not provided.
|
// SetDefaults returns a new Config with default values set where not provided.
|
||||||
@@ -217,7 +220,6 @@ func NewMachine(config *Config) (*Machine, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init machine database: %w", err)
|
return nil, fmt.Errorf("init machine database: %w", err)
|
||||||
}
|
}
|
||||||
dockerServer := machinedocker.NewServer(dockerCli, db)
|
|
||||||
|
|
||||||
// Init a local gRPC proxy server that proxies requests to the local or remote machine API servers.
|
// Init a local gRPC proxy server that proxies requests to the local or remote machine API servers.
|
||||||
proxyDirector := apiproxy.NewDirector(config.MachineSockPath, APIPort)
|
proxyDirector := apiproxy.NewDirector(config.MachineSockPath, APIPort)
|
||||||
@@ -235,11 +237,16 @@ func NewMachine(config *Config) (*Machine, error) {
|
|||||||
initialised: make(chan struct{}, 1),
|
initialised: make(chan struct{}, 1),
|
||||||
store: corroStore,
|
store: corroStore,
|
||||||
cluster: c,
|
cluster: c,
|
||||||
docker: dockerServer,
|
|
||||||
localProxyServer: localProxyServer,
|
localProxyServer: localProxyServer,
|
||||||
proxyDirector: proxyDirector,
|
proxyDirector: proxyDirector,
|
||||||
}
|
}
|
||||||
m.localMachineServer = newGRPCServer(m, c, dockerServer)
|
|
||||||
|
// Machine IP will only be available after the machine is initialised as a cluster member so wrap it in a function.
|
||||||
|
internalDNSIP := func() netip.Addr {
|
||||||
|
return m.IP()
|
||||||
|
}
|
||||||
|
m.docker = machinedocker.NewServer(dockerCli, db, internalDNSIP)
|
||||||
|
m.localMachineServer = newGRPCServer(m, c, m.docker)
|
||||||
|
|
||||||
if m.Initialised() {
|
if m.Initialised() {
|
||||||
m.initialised <- struct{}{}
|
m.initialised <- struct{}{}
|
||||||
@@ -270,6 +277,15 @@ func (m *Machine) Initialised() bool {
|
|||||||
return m.state.ID != ""
|
return m.state.ID != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IP returns the machine IPv4 address in the cluster network which is the first address in the machine subnet.
|
||||||
|
func (m *Machine) IP() netip.Addr {
|
||||||
|
if !m.Initialised() {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return network.MachineIP(m.state.Network.Subnet)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Machine) Run(ctx context.Context) error {
|
func (m *Machine) Run(ctx context.Context) error {
|
||||||
// Docker dependency is essential for the machine to function. Block until it's ready.
|
// Docker dependency is essential for the machine to function. Block until it's ready.
|
||||||
if err := docker.WaitDaemonReady(ctx, m.config.DockerClient); err != nil {
|
if err := docker.WaitDaemonReady(ctx, m.config.DockerClient); err != nil {
|
||||||
@@ -373,6 +389,12 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
return fmt.Errorf("create Caddyfile controller: %w", err)
|
return fmt.Errorf("create Caddyfile controller: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnsResolver := dns.NewClusterResolver(m.store)
|
||||||
|
dnsServer, err := dns.NewServer(m.IP(), dnsResolver, m.config.DNSUpstreams)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create embedded DNS server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
ctrl, err = newNetworkController(
|
ctrl, err = newNetworkController(
|
||||||
m.state,
|
m.state,
|
||||||
m.store,
|
m.store,
|
||||||
@@ -380,6 +402,8 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
m.config.CorrosionService,
|
m.config.CorrosionService,
|
||||||
m.config.DockerClient,
|
m.config.DockerClient,
|
||||||
caddyfileCtrl,
|
caddyfileCtrl,
|
||||||
|
dnsServer,
|
||||||
|
dnsResolver,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initialise network controller: %w", err)
|
return fmt.Errorf("initialise network controller: %w", err)
|
||||||
@@ -520,6 +544,38 @@ func (m *Machine) configureCorrosion() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckPrerequisites verifies if the machine meets all necessary system requirements to participate in the cluster.
|
||||||
|
func (m *Machine) CheckPrerequisites(ctx context.Context, _ *emptypb.Empty) (*pb.CheckPrerequisitesResponse, error) {
|
||||||
|
// Check DNS port (UDP) availability.
|
||||||
|
if err := checkDNSPortAvailable(); err != nil {
|
||||||
|
return &pb.CheckPrerequisitesResponse{
|
||||||
|
Satisfied: false,
|
||||||
|
Error: err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.CheckPrerequisitesResponse{
|
||||||
|
Satisfied: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkDNSPortAvailable verifies that DNS port 53/udp is available for Uncloud's embedded DNS service.
|
||||||
|
func checkDNSPortAvailable() error {
|
||||||
|
addr := &net.UDPAddr{
|
||||||
|
IP: net.IPv4(127, 0, 0, 210), // Use a unique loopback address to avoid conflicts.
|
||||||
|
Port: dns.Port,
|
||||||
|
}
|
||||||
|
conn, err := net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("DNS port %d/udp is already in use by another service: %w. Uncloud needs this port "+
|
||||||
|
"to run the embedded internal DNS service on WireGuard interface 'uncloud'. Please reconfigure "+
|
||||||
|
"any DNS servers (like dnsmasq, systemd-resolved, or named) that might be listening on all network "+
|
||||||
|
"interfaces (0.0.0.0) on the machine and try again", dns.Port, err)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// InitCluster initialises a new cluster on the local machine with the provided network configuration.
|
// InitCluster initialises a new cluster on the local machine with the provided network configuration.
|
||||||
func (m *Machine) InitCluster(ctx context.Context, req *pb.InitClusterRequest) (*pb.InitClusterResponse, error) {
|
func (m *Machine) InitCluster(ctx context.Context, req *pb.InitClusterRequest) (*pb.InitClusterResponse, error) {
|
||||||
if m.Initialised() {
|
if m.Initialised() {
|
||||||
|
|||||||
+84
-66
@@ -4,22 +4,25 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cenkalti/backoff/v4"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/machine/caddyfile"
|
"github.com/psviderski/uncloud/internal/machine/caddyfile"
|
||||||
"github.com/psviderski/uncloud/internal/machine/corroservice"
|
"github.com/psviderski/uncloud/internal/machine/corroservice"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/dns"
|
||||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
"github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/firewall"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/psviderski/uncloud/internal/machine/store"
|
"github.com/psviderski/uncloud/internal/machine/store"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,8 +41,9 @@ type networkController struct {
|
|||||||
dockerCli *client.Client
|
dockerCli *client.Client
|
||||||
caddyfileCtrl *caddyfile.Controller
|
caddyfileCtrl *caddyfile.Controller
|
||||||
|
|
||||||
// TODO: DNS server/resolver listening on the machine IP, e.g. 10.210.0.1:53. It can't listen on 127.0.X.X
|
// dnsServer is the embedded internal DNS server for the cluster listening on the machine IP.
|
||||||
// like resolved does because it needs to be reachable from both the host and the containers.
|
dnsServer *dns.Server
|
||||||
|
dnsResolver *dns.ClusterResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNetworkController(
|
func newNetworkController(
|
||||||
@@ -49,6 +53,8 @@ func newNetworkController(
|
|||||||
corroService corroservice.Service,
|
corroService corroservice.Service,
|
||||||
dockerCli *client.Client,
|
dockerCli *client.Client,
|
||||||
caddyfileCtrl *caddyfile.Controller,
|
caddyfileCtrl *caddyfile.Controller,
|
||||||
|
dnsServer *dns.Server,
|
||||||
|
dnsResolver *dns.ClusterResolver,
|
||||||
) (
|
) (
|
||||||
*networkController, error,
|
*networkController, error,
|
||||||
) {
|
) {
|
||||||
@@ -68,10 +74,16 @@ func newNetworkController(
|
|||||||
corroService: corroService,
|
corroService: corroService,
|
||||||
dockerCli: dockerCli,
|
dockerCli: dockerCli,
|
||||||
caddyfileCtrl: caddyfileCtrl,
|
caddyfileCtrl: caddyfileCtrl,
|
||||||
|
dnsServer: dnsServer,
|
||||||
|
dnsResolver: dnsResolver,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *networkController) Run(ctx context.Context) error {
|
func (nc *networkController) Run(ctx context.Context) error {
|
||||||
|
if err := firewall.ConfigureIptablesChains(); err != nil {
|
||||||
|
return fmt.Errorf("configure iptables chains: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := nc.wgnet.Configure(*nc.state.Network); err != nil {
|
if err := nc.wgnet.Configure(*nc.state.Network); err != nil {
|
||||||
return fmt.Errorf("configure WireGuard network: %w", err)
|
return fmt.Errorf("configure WireGuard network: %w", err)
|
||||||
}
|
}
|
||||||
@@ -110,65 +122,73 @@ func (nc *networkController) Run(ctx context.Context) error {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
errGroup.Go(func() error {
|
||||||
|
slog.Info("Starting embedded DNS resolver.")
|
||||||
|
if err := nc.dnsResolver.Run(ctx); err != nil {
|
||||||
|
return fmt.Errorf("embedded DNS resolver failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
errGroup.Go(func() error {
|
||||||
|
slog.Info("Starting embedded DNS server.")
|
||||||
|
if err := nc.dnsServer.Run(ctx); err != nil {
|
||||||
|
return fmt.Errorf("embedded DNS server failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
// Setup Docker network and synchronise containers to the cluster store.
|
// Setup Docker network and synchronise containers to the cluster store.
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
return nc.prepareAndWatchDocker(ctx)
|
||||||
return nc.prepareAndWatchDocker(ctx)
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handle machine changes in the cluster. Handling machine and endpoint changes should be done
|
// Handle machine changes in the cluster. Handling machine and endpoint changes should be done
|
||||||
// in separate goroutines to avoid a deadlock when reconfiguring the network.
|
// in separate goroutines to avoid a deadlock when reconfiguring the network.
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
if err := nc.handleMachineChanges(ctx); err != nil {
|
||||||
if err := nc.handleMachineChanges(ctx); err != nil {
|
return fmt.Errorf("handle new machines: %w", err)
|
||||||
return fmt.Errorf("handle new machines: %w", err)
|
}
|
||||||
}
|
return nil
|
||||||
return nil
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watch for endpoint changes and update the machine state accordingly.
|
// Watch for endpoint changes and update the machine state accordingly.
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
for {
|
||||||
for {
|
select {
|
||||||
select {
|
case e, ok := <-nc.endpointChanges:
|
||||||
case e, ok := <-nc.endpointChanges:
|
if !ok {
|
||||||
if !ok {
|
// The channel was closed, stop watching for changes.
|
||||||
// The channel was closed, stop watching for changes.
|
nc.endpointChanges = nil
|
||||||
nc.endpointChanges = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nc.state.mu.Lock()
|
|
||||||
for i := range nc.state.Network.Peers {
|
|
||||||
if nc.state.Network.Peers[i].PublicKey.Equal(e.PublicKey) {
|
|
||||||
nc.state.Network.Peers[i].Endpoint = &e.Endpoint
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := nc.state.Save(); err != nil {
|
|
||||||
slog.Error("Failed to save machine state.", "err", err)
|
|
||||||
}
|
|
||||||
nc.state.mu.Unlock()
|
|
||||||
|
|
||||||
slog.Debug("Preserved endpoint change in the machine state.",
|
|
||||||
"public_key", e.PublicKey, "endpoint", e.Endpoint)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
errGroup.Go(
|
nc.state.mu.Lock()
|
||||||
func() error {
|
for i := range nc.state.Network.Peers {
|
||||||
if err := nc.wgnet.Run(ctx); err != nil {
|
if nc.state.Network.Peers[i].PublicKey.Equal(e.PublicKey) {
|
||||||
return fmt.Errorf("WireGuard network failed: %w", err)
|
nc.state.Network.Peers[i].Endpoint = &e.Endpoint
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := nc.state.Save(); err != nil {
|
||||||
|
slog.Error("Failed to save machine state.", "err", err)
|
||||||
|
}
|
||||||
|
nc.state.mu.Unlock()
|
||||||
|
|
||||||
|
slog.Debug("Preserved endpoint change in the machine state.",
|
||||||
|
"public_key", e.PublicKey, "endpoint", e.Endpoint)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
errGroup.Go(func() error {
|
||||||
|
if err := nc.wgnet.Run(ctx); err != nil {
|
||||||
|
return fmt.Errorf("WireGuard network failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
slog.Info("Starting Caddyfile controller.")
|
slog.Info("Starting Caddyfile controller.")
|
||||||
@@ -180,16 +200,14 @@ func (nc *networkController) Run(ctx context.Context) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Wait for the context to be done and stop the network API server.
|
// Wait for the context to be done and stop the network API server.
|
||||||
errGroup.Go(
|
errGroup.Go(func() error {
|
||||||
func() error {
|
<-ctx.Done()
|
||||||
<-ctx.Done()
|
slog.Info("Stopping network API server.")
|
||||||
slog.Info("Stopping network API server.")
|
// TODO: implement timeout for graceful shutdown.
|
||||||
// TODO: implement timeout for graceful shutdown.
|
nc.server.GracefulStop()
|
||||||
nc.server.GracefulStop()
|
slog.Info("Network API server stopped.")
|
||||||
slog.Info("Network API server stopped.")
|
return nil
|
||||||
return nil
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return errGroup.Wait()
|
return errGroup.Wait()
|
||||||
}
|
}
|
||||||
@@ -202,7 +220,7 @@ func (nc *networkController) prepareAndWatchDocker(ctx context.Context) error {
|
|||||||
return fmt.Errorf("wait for Docker daemon: %w", err)
|
return fmt.Errorf("wait for Docker daemon: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.EnsureUncloudNetwork(ctx, nc.state.Network.Subnet); err != nil {
|
if err := manager.EnsureUncloudNetwork(ctx, nc.state.Network.Subnet, nc.dnsServer.ListenAddr()); err != nil {
|
||||||
return fmt.Errorf("ensure Docker network: %w", err)
|
return fmt.Errorf("ensure Docker network: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("Docker network configured.")
|
slog.Info("Docker network configured.")
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package network
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -13,7 +14,7 @@ type Config struct {
|
|||||||
// in the subnet. Other IP addresses are allocated to containers running on the machine.
|
// in the subnet. Other IP addresses are allocated to containers running on the machine.
|
||||||
Subnet netip.Prefix
|
Subnet netip.Prefix
|
||||||
// ManagementIP is the IPv6 address assigned to the machine within the WireGuard network. This address is used
|
// ManagementIP is the IPv6 address assigned to the machine within the WireGuard network. This address is used
|
||||||
// for cluster management traffic, such as gRPC communication with the machine API server and Serf gossip.
|
// for cluster management traffic, such as gRPC communication with the machine API server and Corrosion gossip.
|
||||||
ManagementIP netip.Addr
|
ManagementIP netip.Addr
|
||||||
PrivateKey secret.Secret
|
PrivateKey secret.Secret
|
||||||
PublicKey secret.Secret
|
PublicKey secret.Secret
|
||||||
@@ -23,7 +24,7 @@ type Config struct {
|
|||||||
type PeerConfig struct {
|
type PeerConfig struct {
|
||||||
Subnet *netip.Prefix `json:",omitempty"`
|
Subnet *netip.Prefix `json:",omitempty"`
|
||||||
// ManagementIP is the IPv6 address assigned to the peer within the WireGuard network. This address is used
|
// ManagementIP is the IPv6 address assigned to the peer within the WireGuard network. This address is used
|
||||||
// for cluster management traffic, such as gRPC communication with the machine API server and Serf gossip.
|
// for cluster management traffic, such as gRPC communication with the machine API server and Corrosion gossip.
|
||||||
ManagementIP netip.Addr
|
ManagementIP netip.Addr
|
||||||
Endpoint *netip.AddrPort `json:",omitempty"`
|
Endpoint *netip.AddrPort `json:",omitempty"`
|
||||||
AllEndpoints []netip.AddrPort `json:",omitempty"`
|
AllEndpoints []netip.AddrPort `json:",omitempty"`
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package network
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MachineIP returns the IP address of the machine which is the first address in the subnet.
|
// MachineIP returns the IP address of the machine which is the first address in the subnet.
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
var version string
|
||||||
|
|
||||||
|
func String() string {
|
||||||
|
return version
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,6 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// DockerNetworkName is the name of the Docker network used by uncloud. Keep the value in sync with NetworkName
|
||||||
|
// in internal/machine/docker/manager.go.
|
||||||
|
DockerNetworkName = "uncloud"
|
||||||
LabelManaged = "uncloud.managed"
|
LabelManaged = "uncloud.managed"
|
||||||
LabelServiceID = "uncloud.service.id"
|
LabelServiceID = "uncloud.service.id"
|
||||||
LabelServiceName = "uncloud.service.name"
|
LabelServiceName = "uncloud.service.name"
|
||||||
@@ -90,6 +94,22 @@ func (c *Container) HumanState() (string, error) {
|
|||||||
c.State.ExitCode, units.HumanDuration(time.Now().UTC().Sub(finishedAt))), nil
|
c.State.ExitCode, units.HumanDuration(time.Now().UTC().Sub(finishedAt))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UncloudNetworkIP returns the IP address of the container in the uncloud Docker network.
|
||||||
|
func (c *Container) UncloudNetworkIP() netip.Addr {
|
||||||
|
network, ok := c.NetworkSettings.Networks[DockerNetworkName]
|
||||||
|
if !ok {
|
||||||
|
// Container is not connected to the uncloud Docker network (could be host network).
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := netip.ParseAddr(network.IPAddress)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) UnmarshalJSON(data []byte) error {
|
func (c *Container) UnmarshalJSON(data []byte) error {
|
||||||
// A temporary type that's identical to Container but doesn't have the UnmarshalJSON method.
|
// A temporary type that's identical to Container but doesn't have the UnmarshalJSON method.
|
||||||
type ContainerAlias Container
|
type ContainerAlias Container
|
||||||
|
|||||||
+11
-2
@@ -31,6 +31,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var serviceIDRegexp = regexp.MustCompile("^[0-9a-f]{32}$")
|
var serviceIDRegexp = regexp.MustCompile("^[0-9a-f]{32}$")
|
||||||
|
var dnsLabelRegexp = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`)
|
||||||
|
|
||||||
func ValidateServiceID(id string) bool {
|
func ValidateServiceID(id string) bool {
|
||||||
return serviceIDRegexp.MatchString(id)
|
return serviceIDRegexp.MatchString(id)
|
||||||
@@ -103,8 +104,15 @@ func (s *ServiceSpec) Validate() error {
|
|||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid mode: %q", s.Mode)
|
return fmt.Errorf("invalid mode: %q", s.Mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: validate the service name is a valid DNS label.
|
if s.Name != "" {
|
||||||
|
if len(s.Name) > 63 {
|
||||||
|
return fmt.Errorf("service name too long (max 63 characters): %q", s.Name)
|
||||||
|
}
|
||||||
|
if !dnsLabelRegexp.MatchString(s.Name) {
|
||||||
|
return fmt.Errorf("invalid service name: %q. must be 1-63 characters, lowercase letters, numbers, and dashes only; must start and end with a letter or number", s.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range s.Ports {
|
for _, p := range s.Ports {
|
||||||
if (p.Mode == "" || p.Mode == PortModeIngress) &&
|
if (p.Mode == "" || p.Mode == PortModeIngress) &&
|
||||||
@@ -138,6 +146,7 @@ func (s *ServiceSpec) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *ServiceSpec) Clone() ServiceSpec {
|
func (s *ServiceSpec) Clone() ServiceSpec {
|
||||||
spec := *s
|
spec := *s
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -24,12 +24,12 @@ var caddyImageTagRegex = regexp.MustCompile(`^2\.\d+\.\d+$`)
|
|||||||
// The service is deployed in global mode to all machines in the cluster. If the image is not provided, the latest
|
// The service is deployed in global mode to all machines in the cluster. If the image is not provided, the latest
|
||||||
// version of the official Caddy Docker image is used.
|
// version of the official Caddy Docker image is used.
|
||||||
func (cli *Client) NewCaddyDeployment(image string, placement api.Placement) (*deploy.Deployment, error) {
|
func (cli *Client) NewCaddyDeployment(image string, placement api.Placement) (*deploy.Deployment, error) {
|
||||||
latest, err := LatestCaddyImage()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("look up latest Caddy image: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if image == "" {
|
if image == "" {
|
||||||
|
latest, err := LatestCaddyImage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("look up latest Caddy image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
image = reference.FamiliarString(latest)
|
image = reference.FamiliarString(latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ import (
|
|||||||
// FakeProjectName is a placeholder name for the project to be able to strip it from the resource names used as prefix.
|
// FakeProjectName is a placeholder name for the project to be able to strip it from the resource names used as prefix.
|
||||||
const FakeProjectName = "f-a-k-e"
|
const FakeProjectName = "f-a-k-e"
|
||||||
|
|
||||||
func LoadProject(ctx context.Context, paths []string) (*types.Project, error) {
|
func LoadProject(ctx context.Context, paths []string, opts ...composecli.ProjectOptionsFn) (*types.Project, error) {
|
||||||
options, err := composecli.NewProjectOptions(
|
defaultOpts := []composecli.ProjectOptionsFn{
|
||||||
paths,
|
|
||||||
composecli.WithName(FakeProjectName),
|
composecli.WithName(FakeProjectName),
|
||||||
// First apply os.Environment, always wins.
|
// First apply os.Environment, always wins.
|
||||||
composecli.WithOsEnv,
|
composecli.WithOsEnv,
|
||||||
@@ -23,9 +22,14 @@ func LoadProject(ctx context.Context, paths []string) (*types.Project, error) {
|
|||||||
composecli.WithDotEnv,
|
composecli.WithDotEnv,
|
||||||
// Get compose file path set by COMPOSE_FILE.
|
// Get compose file path set by COMPOSE_FILE.
|
||||||
composecli.WithConfigFileEnv,
|
composecli.WithConfigFileEnv,
|
||||||
// If none was selected, get default compose-ports-long.yaml file from current dir or parent folders.
|
// If none was selected, get default Compose file names from current or parent folders.
|
||||||
composecli.WithDefaultConfigPath,
|
composecli.WithDefaultConfigPath,
|
||||||
composecli.WithExtension(PortsExtensionKey, PortsSource{}),
|
composecli.WithExtension(PortsExtensionKey, PortsSource{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
options, err := composecli.NewProjectOptions(
|
||||||
|
paths,
|
||||||
|
append(defaultOpts, opts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create compose parser options: %w", err)
|
return nil, fmt.Errorf("create compose parser options: %w", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user