mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
chore: rename cluster term to context in CLI and config, convert config toml -> yaml
This commit is contained in:
@@ -31,7 +31,7 @@ reset-dev:
|
|||||||
ssh ubuntu@152.67.101.197 "sudo systemctl stop uncloud && sudo rm -rf /var/lib/uncloud"
|
ssh ubuntu@152.67.101.197 "sudo systemctl stop uncloud && sudo rm -rf /var/lib/uncloud"
|
||||||
|
|
||||||
demo-reset:
|
demo-reset:
|
||||||
rm -fv ~/.config/uncloud/config.toml
|
rm -fv ~/.config/uncloud/config.yaml
|
||||||
ssh ubuntu@152.67.101.197 "AUTO_CONFIRM=true sudo -E uncloud-uninstall && docker rmi caddy:2.9.1"
|
ssh ubuntu@152.67.101.197 "AUTO_CONFIRM=true sudo -E uncloud-uninstall && docker rmi caddy:2.9.1"
|
||||||
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"
|
||||||
|
|||||||
+5
-4
@@ -3,12 +3,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/client"
|
||||||
"github.com/psviderski/uncloud/cmd/ucind/cluster"
|
"github.com/psviderski/uncloud/cmd/ucind/cluster"
|
||||||
"github.com/psviderski/uncloud/internal/ucind"
|
"github.com/psviderski/uncloud/internal/ucind"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -44,9 +45,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: allow to override using UNCLOUD_CONFIG env var.
|
// TODO: allow to override using UNCLOUD_CONFIG env var.
|
||||||
cmd.PersistentFlags().StringVar(&configPath, "uncloud-config", "~/.config/uncloud/config.toml",
|
cmd.PersistentFlags().StringVar(&configPath, "uncloud-config", "~/.config/uncloud/config.yaml",
|
||||||
"path to the Uncloud configuration file.")
|
"path to the Uncloud configuration file.")
|
||||||
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "toml")
|
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml")
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
cluster.NewRootCommand(),
|
cluster.NewRootCommand(),
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
@@ -12,15 +16,12 @@ import (
|
|||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"maps"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type deployOptions struct {
|
type deployOptions struct {
|
||||||
image string
|
image string
|
||||||
machine string
|
machine string
|
||||||
cluster string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeployCommand() *cobra.Command {
|
func NewDeployCommand() *cobra.Command {
|
||||||
@@ -42,15 +43,15 @@ func NewDeployCommand() *cobra.Command {
|
|||||||
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
|
||||||
"Machine names to deploy to (comma-separated). (default is all machines)")
|
"Machine names to deploy to (comma-separated). (default is all machines)")
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.context, "context", "c", "",
|
||||||
"Name of the cluster to deploy to. (default is the current cluster)",
|
"Name of the cluster context to deploy to. (default is the current context)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
||||||
clusterClient, err := uncli.ConnectCluster(ctx, opts.cluster)
|
clusterClient, err := uncli.ConnectCluster(ctx, opts.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
"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"
|
||||||
@@ -11,14 +13,13 @@ import (
|
|||||||
"github.com/psviderski/uncloud/pkg/client/compose"
|
"github.com/psviderski/uncloud/pkg/client/compose"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type deployOptions struct {
|
type deployOptions struct {
|
||||||
files []string
|
files []string
|
||||||
services []string
|
services []string
|
||||||
|
|
||||||
cluster string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeployCommand creates a new command to deploy services from a Compose file.
|
// NewDeployCommand creates a new command to deploy services from a Compose file.
|
||||||
@@ -41,8 +42,8 @@ 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-ports-long.yaml)")
|
||||||
cmd.Flags().StringVarP(&opts.cluster, "cluster", "c", "",
|
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
|
||||||
"Name of the cluster to deploy to (default is the current cluster)")
|
"Name of the cluster context to deploy to (default is the current context)")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -62,7 +63,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterClient, err := uncli.ConnectCluster(ctx, opts.cluster)
|
clusterClient, err := uncli.ConnectCluster(ctx, opts.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type releaseOptions struct {
|
type releaseOptions struct {
|
||||||
cluster string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReleaseCommand() *cobra.Command {
|
func NewReleaseCommand() *cobra.Command {
|
||||||
@@ -28,15 +29,15 @@ func NewReleaseCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.context, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func release(ctx context.Context, uncli *cli.CLI, opts releaseOptions) error {
|
func release(ctx context.Context, uncli *cli.CLI, opts releaseOptions) error {
|
||||||
client, err := uncli.ConnectCluster(ctx, opts.cluster)
|
client, err := uncli.ConnectCluster(ctx, opts.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
@@ -18,7 +19,7 @@ const DefaultUncloudDNSAPIEndpoint = "https://dns.uncloud.run/v1"
|
|||||||
|
|
||||||
type reserveOptions struct {
|
type reserveOptions struct {
|
||||||
endpoint string
|
endpoint string
|
||||||
cluster string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReserveCommand() *cobra.Command {
|
func NewReserveCommand() *cobra.Command {
|
||||||
@@ -36,15 +37,15 @@ func NewReserveCommand() *cobra.Command {
|
|||||||
cmd.Flags().StringVar(&opts.endpoint, "endpoint", DefaultUncloudDNSAPIEndpoint,
|
cmd.Flags().StringVar(&opts.endpoint, "endpoint", DefaultUncloudDNSAPIEndpoint,
|
||||||
"API endpoint for the Uncloud DNS service.")
|
"API endpoint for the Uncloud DNS service.")
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.context, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func reserve(ctx context.Context, uncli *cli.CLI, opts reserveOptions) error {
|
func reserve(ctx context.Context, uncli *cli.CLI, opts reserveOptions) error {
|
||||||
clusterClient, err := uncli.ConnectCluster(ctx, opts.cluster)
|
clusterClient, err := uncli.ConnectCluster(ctx, opts.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type showOptions struct {
|
type showOptions struct {
|
||||||
cluster string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShowCommand() *cobra.Command {
|
func NewShowCommand() *cobra.Command {
|
||||||
@@ -26,15 +27,15 @@ func NewShowCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.context, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func show(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
func show(ctx context.Context, uncli *cli.CLI, opts showOptions) error {
|
||||||
clusterClient, err := uncli.ConnectCluster(ctx, opts.cluster)
|
clusterClient, err := uncli.ConnectCluster(ctx, opts.context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to cluster: %w", err)
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
||||||
@@ -16,8 +19,6 @@ import (
|
|||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type addOptions struct {
|
type addOptions struct {
|
||||||
@@ -25,7 +26,7 @@ type addOptions struct {
|
|||||||
noCaddy bool
|
noCaddy bool
|
||||||
publicIP string
|
publicIP string
|
||||||
sshKey string
|
sshKey string
|
||||||
cluster string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddCommand() *cobra.Command {
|
func NewAddCommand() *cobra.Command {
|
||||||
@@ -66,8 +67,8 @@ func NewAddCommand() *cobra.Command {
|
|||||||
"path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
"path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.context, "context", "c", "",
|
||||||
"Name of the cluster to add the machine to. (default is the current cluster)",
|
"Name of the cluster context to add the machine to. (default is the current context)",
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine cli.RemoteMachine, o
|
|||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
|
|
||||||
machineClient, err := uncli.AddMachine(ctx, remoteMachine, opts.cluster, opts.name, publicIP)
|
machineClient, err := uncli.AddMachine(ctx, remoteMachine, opts.context, opts.name, publicIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package machine
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
|
||||||
"github.com/psviderski/uncloud/cmd/uncloud/dns"
|
"github.com/psviderski/uncloud/cmd/uncloud/dns"
|
||||||
@@ -11,7 +13,6 @@ import (
|
|||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/machine/cluster"
|
"github.com/psviderski/uncloud/internal/machine/cluster"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"net/netip"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type initOptions struct {
|
type initOptions struct {
|
||||||
@@ -22,15 +23,16 @@ type initOptions struct {
|
|||||||
noDNS bool
|
noDNS bool
|
||||||
publicIP string
|
publicIP string
|
||||||
sshKey string
|
sshKey string
|
||||||
cluster string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInitCommand() *cobra.Command {
|
func NewInitCommand() *cobra.Command {
|
||||||
opts := initOptions{}
|
opts := initOptions{}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "init [USER@HOST:PORT]",
|
Use: "init [USER@HOST:PORT]",
|
||||||
Short: "Initialise a new cluster that consists of the local or remote machine.",
|
Short: "Initialise a new cluster with a remote machine as the first member.",
|
||||||
// TODO: include usage examples of initialising a local and remote machine.
|
// TODO: include usage examples of initialising a remote machine.
|
||||||
|
// TODO: support initialising a cluster on the local machine.
|
||||||
Args: cobra.MaximumNArgs(1),
|
Args: cobra.MaximumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
@@ -80,8 +82,8 @@ func NewInitCommand() *cobra.Command {
|
|||||||
"Path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
"Path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.context, "context", "c", "",
|
||||||
"Name of the cluster in the local config if initialising a remote machine.",
|
"Name of the cluster context in the local config.",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
@@ -107,7 +109,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := uncli.InitCluster(ctx, remoteMachine, opts.cluster, opts.name, netPrefix, publicIP)
|
client, err := uncli.InitCluster(ctx, remoteMachine, opts.context, opts.name, netPrefix, publicIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,29 +3,30 @@ package machine
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewListCommand() *cobra.Command {
|
func NewListCommand() *cobra.Command {
|
||||||
var cluster string
|
var clusterContext string
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls",
|
Use: "ls",
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"list"},
|
||||||
Short: "List machines in a cluster.",
|
Short: "List machines in a cluster.",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
return list(cmd.Context(), uncli, cluster)
|
return list(cmd.Context(), uncli, clusterContext)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&cluster, "cluster", "c", "",
|
&clusterContext, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -53,7 +53,7 @@ func main() {
|
|||||||
configPath := fs.ExpandHomeDir(opts.configPath)
|
configPath := fs.ExpandHomeDir(opts.configPath)
|
||||||
uncli, err := cli.New(configPath, conn)
|
uncli, err := cli.New(configPath, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("initialize CLI: %w", err)
|
return fmt.Errorf("initialise CLI: %w", err)
|
||||||
}
|
}
|
||||||
cmd.SetContext(context.WithValue(cmd.Context(), "cli", uncli))
|
cmd.SetContext(context.WithValue(cmd.Context(), "cli", uncli))
|
||||||
return nil
|
return nil
|
||||||
@@ -64,9 +64,9 @@ func main() {
|
|||||||
"Connect to a remote cluster machine without using the Uncloud configuration file.\n"+
|
"Connect to a remote cluster machine without using the Uncloud configuration file.\n"+
|
||||||
"Format: [ssh://]user@host[:port] or tcp://host:port")
|
"Format: [ssh://]user@host[:port] or tcp://host:port")
|
||||||
// TODO: allow to override using UNCLOUD_CONFIG env var.
|
// TODO: allow to override using UNCLOUD_CONFIG env var.
|
||||||
cmd.PersistentFlags().StringVar(&opts.configPath, "uncloud-config", "~/.config/uncloud/config.toml",
|
cmd.PersistentFlags().StringVar(&opts.configPath, "uncloud-config", "~/.config/uncloud/config.yaml",
|
||||||
"Path to the Uncloud configuration file.")
|
"Path to the Uncloud configuration file.")
|
||||||
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "toml")
|
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml")
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
NewDeployCommand(),
|
NewDeployCommand(),
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inspectOptions struct {
|
type inspectOptions struct {
|
||||||
@@ -31,8 +32,8 @@ func NewInspectCommand() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.cluster, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewListCommand() *cobra.Command {
|
func NewListCommand() *cobra.Command {
|
||||||
@@ -22,8 +23,8 @@ func NewListCommand() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&cluster, "cluster", "c", "",
|
&cluster, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type rmOptions struct {
|
type rmOptions struct {
|
||||||
@@ -29,8 +30,8 @@ func NewRmCommand() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.cluster, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type runOptions struct {
|
type runOptions struct {
|
||||||
@@ -79,8 +80,8 @@ func NewRunCommand() *cobra.Command {
|
|||||||
"/host/path:/container/path[:ro]. Can be specified multiple times.")
|
"/host/path:/container/path[:ro]. Can be specified multiple times.")
|
||||||
|
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.cluster, "context", "c", "",
|
||||||
"Name of the cluster to run the service in. (default is the current cluster)",
|
"Name of the cluster context to run the service in. (default is the current context)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ func NewScaleCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.cluster, "cluster", "c", "",
|
&opts.cluster, "context", "c", "",
|
||||||
"Name of the cluster. (default is the current cluster)",
|
"Name of the cluster context. (default is the current context)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ In addition, any ports for any workloads you want to expose will need to be open
|
|||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
Uncloud stores its configuration in `~/.config/uncloud/config.toml`. If you wish to reinitialise a cluster, simply remove it from this config.
|
Uncloud stores its configuration in `~/.config/uncloud/config.yaml`. If you wish to reinitialise a cluster, simply remove it from this config.
|
||||||
|
|
||||||
### Initialisation
|
### Initialisation
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576
|
||||||
google.golang.org/grpc v1.68.1
|
google.golang.org/grpc v1.68.1
|
||||||
google.golang.org/protobuf v1.36.3
|
google.golang.org/protobuf v1.36.3
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.36.3
|
modernc.org/sqlite v1.36.3
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -310,7 +311,6 @@ require (
|
|||||||
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
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||||
howett.net/plist v1.0.0 // indirect
|
howett.net/plist v1.0.0 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
|
|||||||
+67
-58
@@ -4,6 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
|
||||||
"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"
|
||||||
@@ -13,14 +16,12 @@ 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"
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
"github.com/charmbracelet/huh"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultClusterName = "default"
|
const defaultContextName = "default"
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
@@ -45,61 +46,68 @@ func New(configPath string, conn *config.MachineConnection) (*CLI, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CLI) CreateCluster(name string) error {
|
func (cli *CLI) CreateContext(name string) error {
|
||||||
if _, ok := cli.config.Clusters[name]; ok {
|
if _, ok := cli.config.Contexts[name]; ok {
|
||||||
return fmt.Errorf("cluster %q already exists", name)
|
return fmt.Errorf("context '%s' already exists", name)
|
||||||
}
|
}
|
||||||
cli.config.Clusters[name] = &config.Cluster{
|
cli.config.Contexts[name] = &config.Context{
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
return cli.config.Save()
|
return cli.config.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CLI) SetCurrentCluster(name string) error {
|
func (cli *CLI) SetCurrentContext(name string) error {
|
||||||
if _, ok := cli.config.Clusters[name]; !ok {
|
if _, ok := cli.config.Contexts[name]; !ok {
|
||||||
return api.ErrNotFound
|
return api.ErrNotFound
|
||||||
}
|
}
|
||||||
cli.config.CurrentCluster = name
|
cli.config.CurrentContext = name
|
||||||
return cli.config.Save()
|
return cli.config.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectCluster connects to a cluster using the given cluster name or the current cluster if not specified.
|
// ConnectCluster connects to a cluster using the given context name or the current context if not specified.
|
||||||
// If the CLI was initialised with a machine connection, the config is ignored and the connection is used instead.
|
// If the CLI was initialised with a machine connection, the config is ignored and the connection is used instead.
|
||||||
func (cli *CLI) ConnectCluster(ctx context.Context, clusterName string) (*client.Client, error) {
|
func (cli *CLI) ConnectCluster(ctx context.Context, contextName string) (*client.Client, error) {
|
||||||
if cli.conn != nil {
|
if cli.conn != nil {
|
||||||
return connectCluster(ctx, *cli.conn)
|
return connectCluster(ctx, *cli.conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cli.config.Clusters) == 0 {
|
if len(cli.config.Contexts) == 0 {
|
||||||
return nil, errors.New(
|
return nil, fmt.Errorf(
|
||||||
"no clusters found in the Uncloud config. " +
|
"no cluster contexts found in the Uncloud config (%s). "+
|
||||||
"Please initialise a cluster with `uncloud machine init` first",
|
"Please initialise a cluster with 'uncloud machine init' first",
|
||||||
|
cli.config.Path(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if clusterName == "" {
|
if contextName == "" {
|
||||||
// If the cluster is not specified, use the current cluster if set.
|
// If the cluster is not specified, use the current cluster if set.
|
||||||
if cli.config.CurrentCluster == "" {
|
if cli.config.CurrentContext == "" {
|
||||||
return nil, errors.New(
|
|
||||||
"the current cluster is not set in the Uncloud config. " +
|
|
||||||
"Please specify a cluster with the --cluster flag or set current_cluster in the config",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if _, ok := cli.config.Clusters[cli.config.CurrentCluster]; !ok {
|
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"current cluster %q not found in the config. "+
|
"the current cluster context is not set in the Uncloud config (%s). "+
|
||||||
"Please specify a cluster with the --cluster flag or update current_cluster in the config",
|
"Please specify the context with the '--context' flag or set 'current_context' in the config",
|
||||||
cli.config.CurrentCluster,
|
cli.config.Path(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
clusterName = cli.config.CurrentCluster
|
if _, ok := cli.config.Contexts[cli.config.CurrentContext]; !ok {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"current cluster context '%s' not found in the Uncloud config (%s). "+
|
||||||
|
"Please specify the context with the '--context' flag or update 'current_context' in the config",
|
||||||
|
cli.config.CurrentContext,
|
||||||
|
cli.config.Path(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
contextName = cli.config.CurrentContext
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, ok := cli.config.Clusters[clusterName]
|
cfg, ok := cli.config.Contexts[contextName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("cluster %q not found in the config", clusterName)
|
return nil, fmt.Errorf("cluster context '%s' not found in the Uncloud config (%s)",
|
||||||
|
contextName, cli.config.Path())
|
||||||
}
|
}
|
||||||
if len(cfg.Connections) == 0 {
|
if len(cfg.Connections) == 0 {
|
||||||
return nil, fmt.Errorf("no connection configurations found for cluster %q in the config", clusterName)
|
return nil, fmt.Errorf(
|
||||||
|
"no connection configurations found for cluster context '%s' in the Uncloud config (%s)",
|
||||||
|
contextName, cli.config.Path(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: iterate over all connections and try to connect to the cluster using the first successful connection.
|
// TODO: iterate over all connections and try to connect to the cluster using the first successful connection.
|
||||||
@@ -107,7 +115,7 @@ func (cli *CLI) ConnectCluster(ctx context.Context, clusterName string) (*client
|
|||||||
|
|
||||||
c, err := connectCluster(ctx, conn)
|
c, err := connectCluster(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("no valid connection configuration found for the cluster")
|
return nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
@@ -141,13 +149,13 @@ func connectCluster(ctx context.Context, conn config.MachineConnection) (*client
|
|||||||
func (cli *CLI) InitCluster(
|
func (cli *CLI) InitCluster(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
remoteMachine *RemoteMachine,
|
remoteMachine *RemoteMachine,
|
||||||
clusterName,
|
contextName,
|
||||||
machineName string,
|
machineName string,
|
||||||
netPrefix netip.Prefix,
|
netPrefix netip.Prefix,
|
||||||
publicIP *netip.Addr,
|
publicIP *netip.Addr,
|
||||||
) (*client.Client, error) {
|
) (*client.Client, error) {
|
||||||
if remoteMachine != nil {
|
if remoteMachine != nil {
|
||||||
return cli.initRemoteMachine(ctx, *remoteMachine, clusterName, machineName, netPrefix, publicIP)
|
return cli.initRemoteMachine(ctx, *remoteMachine, contextName, machineName, netPrefix, publicIP)
|
||||||
}
|
}
|
||||||
// TODO: implement local machine initialisation
|
// TODO: implement local machine initialisation
|
||||||
return nil, fmt.Errorf("local machine initialisation is not implemented yet")
|
return nil, fmt.Errorf("local machine initialisation is not implemented yet")
|
||||||
@@ -156,16 +164,16 @@ func (cli *CLI) InitCluster(
|
|||||||
func (cli *CLI) initRemoteMachine(
|
func (cli *CLI) initRemoteMachine(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
remoteMachine RemoteMachine,
|
remoteMachine RemoteMachine,
|
||||||
clusterName,
|
contextName,
|
||||||
machineName string,
|
machineName string,
|
||||||
netPrefix netip.Prefix,
|
netPrefix netip.Prefix,
|
||||||
publicIP *netip.Addr,
|
publicIP *netip.Addr,
|
||||||
) (*client.Client, error) {
|
) (*client.Client, error) {
|
||||||
if clusterName == "" {
|
if contextName == "" {
|
||||||
clusterName = defaultClusterName
|
contextName = defaultContextName
|
||||||
}
|
}
|
||||||
if _, ok := cli.config.Clusters[clusterName]; ok {
|
if _, ok := cli.config.Contexts[contextName]; ok {
|
||||||
return nil, fmt.Errorf("cluster %q already exists", clusterName)
|
return nil, fmt.Errorf("cluster %q already exists", contextName)
|
||||||
}
|
}
|
||||||
|
|
||||||
machineClient, err := cli.provisionRemoteMachine(ctx, remoteMachine)
|
machineClient, err := cli.provisionRemoteMachine(ctx, remoteMachine)
|
||||||
@@ -207,24 +215,24 @@ func (cli *CLI) initRemoteMachine(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("init cluster: %w", err)
|
return nil, fmt.Errorf("init cluster: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Cluster %q initialised with machine %q\n", clusterName, resp.Machine.Name)
|
fmt.Printf("Cluster initialised with machine '%s' and saved as context '%s' in your local config (%s)\n",
|
||||||
|
resp.Machine.Name, contextName, cli.config.Path())
|
||||||
if err = cli.CreateCluster(clusterName); err != nil {
|
if err = cli.CreateContext(contextName); err != nil {
|
||||||
return nil, fmt.Errorf("save cluster to config: %w", err)
|
return nil, fmt.Errorf("save cluster context to config: %w", err)
|
||||||
}
|
}
|
||||||
// Set the current cluster to the just created one if it is the only cluster in the config.
|
// Set the current cluster to the just created one if it is the only cluster in the config.
|
||||||
if len(cli.config.Clusters) == 1 {
|
if len(cli.config.Contexts) == 1 {
|
||||||
if err = cli.SetCurrentCluster(clusterName); err != nil {
|
if err = cli.SetCurrentContext(contextName); err != nil {
|
||||||
return nil, fmt.Errorf("set current cluster: %w", err)
|
return nil, fmt.Errorf("set current cluster context: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the machine's SSH connection details in the cluster config.
|
// Save the machine's SSH connection details in the context config.
|
||||||
connCfg := config.MachineConnection{
|
connCfg := config.MachineConnection{
|
||||||
SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port),
|
SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port),
|
||||||
SSHKeyFile: remoteMachine.KeyPath,
|
SSHKeyFile: remoteMachine.KeyPath,
|
||||||
}
|
}
|
||||||
cli.config.Clusters[clusterName].Connections = append(cli.config.Clusters[clusterName].Connections, connCfg)
|
cli.config.Contexts[contextName].Connections = append(cli.config.Contexts[contextName].Connections, connCfg)
|
||||||
if err = cli.config.Save(); err != nil {
|
if err = cli.config.Save(); err != nil {
|
||||||
return nil, fmt.Errorf("save config: %w", err)
|
return nil, fmt.Errorf("save config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -234,11 +242,11 @@ func (cli *CLI) initRemoteMachine(
|
|||||||
// AddMachine provisions a remote machine and adds it to the cluster. It returns a client to interact with the machine
|
// AddMachine provisions a remote machine and adds it to the cluster. It returns a client to interact with the machine
|
||||||
// which should be closed after use by the caller.
|
// which should be closed after use by the caller.
|
||||||
func (cli *CLI) AddMachine(
|
func (cli *CLI) AddMachine(
|
||||||
ctx context.Context, remoteMachine RemoteMachine, clusterName, machineName string, publicIP *netip.Addr,
|
ctx context.Context, remoteMachine RemoteMachine, contextName, machineName string, publicIP *netip.Addr,
|
||||||
) (*client.Client, error) {
|
) (*client.Client, error) {
|
||||||
c, err := cli.ConnectCluster(ctx, clusterName)
|
c, err := cli.ConnectCluster(ctx, contextName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("connect to cluster: %w", err)
|
return nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err)
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
@@ -295,7 +303,7 @@ func (cli *CLI) AddMachine(
|
|||||||
|
|
||||||
addResp, err := c.AddMachine(ctx, addReq)
|
addResp, err := c.AddMachine(ctx, addReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("add machine to cluster: %w", err)
|
return nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List other machines in the cluster to include them in the join request.
|
// List other machines in the cluster to include them in the join request.
|
||||||
@@ -319,17 +327,17 @@ func (cli *CLI) AddMachine(
|
|||||||
return nil, fmt.Errorf("join cluster: %w", err)
|
return nil, fmt.Errorf("join cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Machine %q added to cluster\n", addResp.Machine.Name)
|
fmt.Printf("Machine '%s' added to the cluster (context '%s').\n", addResp.Machine.Name, contextName)
|
||||||
|
|
||||||
// Save the machine's SSH connection details in the cluster config.
|
// Save the machine's SSH connection details in the context config.
|
||||||
connCfg := config.MachineConnection{
|
connCfg := config.MachineConnection{
|
||||||
SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port),
|
SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port),
|
||||||
SSHKeyFile: remoteMachine.KeyPath,
|
SSHKeyFile: remoteMachine.KeyPath,
|
||||||
}
|
}
|
||||||
if clusterName == "" {
|
if contextName == "" {
|
||||||
clusterName = cli.config.CurrentCluster
|
contextName = cli.config.CurrentContext
|
||||||
}
|
}
|
||||||
cli.config.Clusters[clusterName].Connections = append(cli.config.Clusters[clusterName].Connections, connCfg)
|
cli.config.Contexts[contextName].Connections = append(cli.config.Contexts[contextName].Connections, connCfg)
|
||||||
if err = cli.config.Save(); err != nil {
|
if err = cli.config.Save(); err != nil {
|
||||||
return nil, fmt.Errorf("save config: %w", err)
|
return nil, fmt.Errorf("save config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -396,7 +404,8 @@ func (cli *CLI) promptResetMachine() error {
|
|||||||
return fmt.Errorf("remote machine is already initialised as a cluster member")
|
return fmt.Errorf("remote machine is already initialised as a cluster member")
|
||||||
}
|
}
|
||||||
// TODO: implement resetting the remote machine.
|
// TODO: implement resetting the remote machine.
|
||||||
return fmt.Errorf("resetting the remote machine is not implemented yet")
|
return fmt.Errorf("resetting the remote machine is not implemented yet. " +
|
||||||
|
"Please manually run 'uncloud-uninstall' on the remote machine to fully uninstall Uncloud from it")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProgressOut returns an output stream for progress writer.
|
// ProgressOut returns an output stream for progress writer.
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
type Cluster struct {
|
|
||||||
Name string `toml:"-"`
|
|
||||||
Connections []MachineConnection `toml:"connections"`
|
|
||||||
}
|
|
||||||
@@ -2,14 +2,15 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Clusters map[string]*Cluster `toml:"clusters"`
|
CurrentContext string `yaml:"current_context"`
|
||||||
CurrentCluster string `toml:"current_cluster"`
|
Contexts map[string]*Context `yaml:"contexts"`
|
||||||
|
|
||||||
// path is the file path config is read from.
|
// path is the file path config is read from.
|
||||||
path string
|
path string
|
||||||
@@ -18,10 +19,10 @@ type Config struct {
|
|||||||
func NewFromFile(path string) (*Config, error) {
|
func NewFromFile(path string) (*Config, error) {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return nil, fmt.Errorf("check file permissions %q: %w", path, err)
|
return nil, fmt.Errorf("check file permissions '%s': %w", path, err)
|
||||||
}
|
}
|
||||||
c := &Config{
|
c := &Config{
|
||||||
Clusters: map[string]*Cluster{},
|
Contexts: map[string]*Context{},
|
||||||
path: path,
|
path: path,
|
||||||
}
|
}
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -34,30 +35,38 @@ func NewFromFile(path string) (*Config, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) Path() string {
|
||||||
|
return c.path
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) Read() error {
|
func (c *Config) Read() error {
|
||||||
_, err := toml.DecodeFile(c.path, c)
|
data, err := os.ReadFile(c.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read config file %q: %w", c.path, err)
|
return fmt.Errorf("read config file '%s': %w", c.path, err)
|
||||||
}
|
}
|
||||||
|
if err = yaml.Unmarshal(data, c); err != nil {
|
||||||
|
return fmt.Errorf("parse config file '%s': %w", c.path, err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Save() error {
|
func (c *Config) Save() error {
|
||||||
dir, _ := filepath.Split(c.path)
|
dir, _ := filepath.Split(c.path)
|
||||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||||
return fmt.Errorf("create config directory %q: %w", dir, err)
|
return fmt.Errorf("create config directory '%s': %w", dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenFile(c.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
f, err := os.OpenFile(c.path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write config file %q: %w", c.path, err)
|
return fmt.Errorf("write config file '%s': %w", c.path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder := toml.NewEncoder(f)
|
encoder := yaml.NewEncoder(f)
|
||||||
encoder.Indent = ""
|
encoder.SetIndent(2)
|
||||||
if err = encoder.Encode(c); err != nil {
|
if err = encoder.Encode(c); err != nil {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
return fmt.Errorf("encode config file %q: %w", c.path, err)
|
return fmt.Errorf("encode config file '%s': %w", c.path, err)
|
||||||
}
|
}
|
||||||
return f.Close()
|
return f.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,11 +15,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MachineConnection struct {
|
type MachineConnection struct {
|
||||||
SSH SSHDestination `toml:"ssh,omitempty"`
|
SSH SSHDestination `yaml:"ssh,omitempty"`
|
||||||
SSHKeyFile string `toml:"ssh_key_file,omitempty"`
|
SSHKeyFile string `yaml:"ssh_key_file,omitempty"`
|
||||||
TCP netip.AddrPort `toml:"tcp,omitempty"`
|
TCP netip.AddrPort `yaml:"tcp,omitempty"`
|
||||||
Host string `toml:"host,omitempty"`
|
Host string `yaml:"host,omitempty"`
|
||||||
PublicKey secret.Secret `toml:"public_key,omitempty"`
|
PublicKey secret.Secret `yaml:"public_key,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSHDestination represents an SSH destination string in the canonical form of "user@host:port".
|
// SSHDestination represents an SSH destination string in the canonical form of "user@host:port".
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
Name string `yaml:"-"`
|
||||||
|
Connections []MachineConnection `yaml:"connections"`
|
||||||
|
}
|
||||||
+12
-11
@@ -2,6 +2,7 @@ package ucind
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,11 +20,11 @@ func (u *ConfigUpdater) AddCluster(c Cluster) error {
|
|||||||
return fmt.Errorf("read Uncloud config: %w", err)
|
return fmt.Errorf("read Uncloud config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := cfg.Clusters[c.Name]; ok {
|
if _, ok := cfg.Contexts[c.Name]; ok {
|
||||||
return fmt.Errorf("cluster '%s' already exists", c.Name)
|
return fmt.Errorf("cluster context '%s' already exists", c.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterCfg := &config.Cluster{
|
clusterCfg := &config.Context{
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
Connections: make([]config.MachineConnection, len(c.Machines)),
|
Connections: make([]config.MachineConnection, len(c.Machines)),
|
||||||
}
|
}
|
||||||
@@ -33,8 +34,8 @@ func (u *ConfigUpdater) AddCluster(c Cluster) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Clusters[c.Name] = clusterCfg
|
cfg.Contexts[c.Name] = clusterCfg
|
||||||
cfg.CurrentCluster = c.Name
|
cfg.CurrentContext = c.Name
|
||||||
|
|
||||||
if err = cfg.Save(); err != nil {
|
if err = cfg.Save(); err != nil {
|
||||||
return fmt.Errorf("save config: %w", err)
|
return fmt.Errorf("save config: %w", err)
|
||||||
@@ -48,17 +49,17 @@ func (u *ConfigUpdater) RemoveCluster(name string) error {
|
|||||||
return fmt.Errorf("read Uncloud config: %w", err)
|
return fmt.Errorf("read Uncloud config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := cfg.Clusters[name]; !ok {
|
if _, ok := cfg.Contexts[name]; !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(cfg.Clusters, name)
|
delete(cfg.Contexts, name)
|
||||||
|
|
||||||
if cfg.CurrentCluster == name {
|
if cfg.CurrentContext == name {
|
||||||
cfg.CurrentCluster = ""
|
cfg.CurrentContext = ""
|
||||||
}
|
}
|
||||||
if _, ok := cfg.Clusters["default"]; ok {
|
if _, ok := cfg.Contexts["default"]; ok {
|
||||||
cfg.CurrentCluster = "default"
|
cfg.CurrentContext = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cfg.Save(); err != nil {
|
if err = cfg.Save(); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user