refactor: Make --context cli flag a global option (#174)

* Make `--context` flag a global option and remove duplication

* Remove unused dns showOptions struct

* First pass of refactoring "active context" determination and validation

* Undo some refactoring in cli.go to keep diff more focused
This commit is contained in:
Justin Bradford
2025-11-19 20:30:32 +10:00
committed by GitHub
parent 1d413734d8
commit 939645e144
27 changed files with 55 additions and 175 deletions
-2
View File
@@ -74,8 +74,6 @@ to cluster machines or --push-registry to upload them to external registries.`,
"Use --machine to specify which machines. (default is all machines)") "Use --machine to specify which machines. (default is all machines)")
cmd.Flags().BoolVar(&opts.PushRegistry, "push-registry", false, cmd.Flags().BoolVar(&opts.PushRegistry, "push-registry", false,
"Upload the built images to external registries (e.g., Docker Hub) after building.") "Upload the built images to external registries (e.g., Docker Hub) after building.")
cmd.Flags().StringVarP(&opts.Context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
return cmd return cmd
} }
+1 -6
View File
@@ -14,7 +14,6 @@ import (
type configOptions struct { type configOptions struct {
machine string machine string
noColor bool noColor bool
context string
} }
func NewConfigCommand() *cobra.Command { func NewConfigCommand() *cobra.Command {
@@ -34,16 +33,12 @@ func NewConfigCommand() *cobra.Command {
"Name or ID of the machine to get the configuration from. (default is connected machine)") "Name or ID of the machine to get the configuration from. (default is connected machine)")
cmd.Flags().BoolVar(&opts.noColor, "no-color", false, cmd.Flags().BoolVar(&opts.noColor, "no-color", false,
"Disable syntax highlighting for the output.") "Disable syntax highlighting for the output.")
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error { func runConfig(ctx context.Context, uncli *cli.CLI, opts configOptions) error {
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -6
View File
@@ -22,7 +22,6 @@ type deployOptions struct {
caddyfile string caddyfile string
image string image string
machines []string machines []string
context string
} }
func NewDeployCommand() *cobra.Command { func NewDeployCommand() *cobra.Command {
@@ -46,10 +45,6 @@ func NewDeployCommand() *cobra.Command {
cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil, cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil,
"Machine names or IDs to deploy to. Can be specified multiple times or as a comma-separated "+ "Machine names or IDs to deploy to. Can be specified multiple times or as a comma-separated "+
"list. (default is all machines)") "list. (default is all machines)")
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context to deploy to. (default is the current context)",
)
return cmd return cmd
} }
@@ -64,7 +59,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
caddyfile = strings.TrimSpace(string(data)) caddyfile = strings.TrimSpace(string(data))
} }
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -5
View File
@@ -26,8 +26,6 @@ type deployOptions struct {
noBuild bool noBuild bool
recreate bool recreate bool
yes bool yes bool
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.
@@ -51,8 +49,6 @@ func NewDeployCommand() *cobra.Command {
"Can be specified multiple times. Format: --build-arg VAR=VALUE") "Can be specified multiple times. Format: --build-arg VAR=VALUE")
cmd.Flags().BoolVar(&opts.BuildServicesOptions.Pull, "build-pull", false, cmd.Flags().BoolVar(&opts.BuildServicesOptions.Pull, "build-pull", false,
"Always attempt to pull newer versions of base images before building service images.") "Always attempt to pull newer versions of base images before building service images.")
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context to deploy to (default is the current context)")
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.yaml)") "One or more Compose files to deploy services from. (default compose.yaml)")
cmd.Flags().BoolVar(&opts.noBuild, "no-build", false, cmd.Flags().BoolVar(&opts.noBuild, "no-build", false,
@@ -108,7 +104,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
fmt.Println() fmt.Println()
} }
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+3 -14
View File
@@ -12,32 +12,21 @@ import (
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
) )
type releaseOptions struct {
context string
}
func NewReleaseCommand() *cobra.Command { func NewReleaseCommand() *cobra.Command {
opts := releaseOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "release", Use: "release",
Short: "Release the reserved cluster domain.", Short: "Release the reserved cluster domain.",
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 release(cmd.Context(), uncli, opts) return release(cmd.Context(), uncli)
}, },
} }
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"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) error {
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -6
View File
@@ -19,7 +19,6 @@ const DefaultUncloudDNSAPIEndpoint = "https://dns.uncloud.run/v1"
type reserveOptions struct { type reserveOptions struct {
endpoint string endpoint string
context string
} }
func NewReserveCommand() *cobra.Command { func NewReserveCommand() *cobra.Command {
@@ -36,16 +35,12 @@ 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(
&opts.context, "context", "c", "",
"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.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+3 -14
View File
@@ -10,32 +10,21 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type showOptions struct {
context string
}
func NewShowCommand() *cobra.Command { func NewShowCommand() *cobra.Command {
opts := showOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "show", Use: "show",
Short: "Print the cluster domain name.", Short: "Print the cluster domain name.",
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 show(cmd.Context(), uncli, opts) return show(cmd.Context(), uncli)
}, },
} }
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"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) error {
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -6
View File
@@ -22,7 +22,6 @@ import (
type listOptions struct { type listOptions struct {
machines []string machines []string
nameFilter string nameFilter string
context string
} }
func NewListCommand() *cobra.Command { func NewListCommand() *cobra.Command {
@@ -60,10 +59,6 @@ func NewListCommand() *cobra.Command {
cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil, cmd.Flags().StringSliceVarP(&opts.machines, "machine", "m", nil,
"Filter images by machine name or ID. Can be specified multiple times or as a comma-separated list. "+ "Filter images by machine name or ID. Can be specified multiple times or as a comma-separated list. "+
"(default is include all machines)") "(default is include all machines)")
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
@@ -82,7 +77,7 @@ type imageRow struct {
} }
func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error { func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -6
View File
@@ -15,7 +15,6 @@ type pushOptions struct {
image string image string
machines []string machines []string
platform string platform string
context string
} }
func NewPushCommand() *cobra.Command { func NewPushCommand() *cobra.Command {
@@ -54,16 +53,12 @@ The image is uploaded to all cluster machines (default) or the specified machine
"Push a specific platform of a multi-platform image (e.g., linux/amd64, linux/arm64).\n"+ "Push a specific platform of a multi-platform image (e.g., linux/amd64, linux/arm64).\n"+
"Local Docker must be configured to use containerd image store to support multi-platform images.", "Local Docker must be configured to use containerd image store to support multi-platform images.",
) )
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
func push(ctx context.Context, uncli *cli.CLI, opts pushOptions) error { func push(ctx context.Context, uncli *cli.CLI, opts pushOptions) error {
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
-6
View File
@@ -26,7 +26,6 @@ type addOptions struct {
noInstall bool noInstall bool
publicIP string publicIP string
sshKey string sshKey string
context string
version string version string
} }
@@ -89,10 +88,6 @@ Connection methods:
&opts.version, "version", "latest", &opts.version, "version", "latest",
"Version of the Uncloud daemon to install on the machine.", "Version of the Uncloud daemon to install on the machine.",
) )
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context to add the machine to. (default is the current context)",
)
return cmd return cmd
} }
@@ -113,7 +108,6 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
} }
clusterClient, machineClient, err := uncli.AddMachine(ctx, cli.AddMachineOptions{ clusterClient, machineClient, err := uncli.AddMachine(ctx, cli.AddMachineOptions{
Context: opts.context,
MachineName: opts.name, MachineName: opts.name,
PublicIP: publicIP, PublicIP: publicIP,
RemoteMachine: remoteMachine, RemoteMachine: remoteMachine,
+3 -8
View File
@@ -14,25 +14,20 @@ import (
) )
func NewListCommand() *cobra.Command { func NewListCommand() *cobra.Command {
var contextName string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "ls", Use: "ls",
Aliases: []string{"list"}, Aliases: []string{"list"},
Short: "List machines in a cluster.", Short: "List machines in a cluster.",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI) uncli := cmd.Context().Value("cli").(*cli.CLI)
return list(cmd.Context(), uncli, contextName) return list(cmd.Context(), uncli)
}, },
} }
cmd.Flags().StringVarP(
&contextName, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
func list(ctx context.Context, uncli *cli.CLI, clusterName string) error { func list(ctx context.Context, uncli *cli.CLI) error {
client, err := uncli.ConnectCluster(ctx, clusterName) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+3 -8
View File
@@ -9,7 +9,6 @@ import (
) )
func NewRenameCommand() *cobra.Command { func NewRenameCommand() *cobra.Command {
var contextName string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rename OLD_NAME NEW_NAME", Use: "rename OLD_NAME NEW_NAME",
Short: "Rename a machine in the cluster.", Short: "Rename a machine in the cluster.",
@@ -20,18 +19,14 @@ configuration including network settings, public IP, and cluster membership.`,
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2),
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 rename(cmd.Context(), uncli, contextName, args[0], args[1]) return rename(cmd.Context(), uncli, args[0], args[1])
}, },
} }
cmd.Flags().StringVarP(
&contextName, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
func rename(ctx context.Context, uncli *cli.CLI, contextName, oldName, newName string) error { func rename(ctx context.Context, uncli *cli.CLI, oldName, newName string) error {
client, err := uncli.ConnectCluster(ctx, contextName) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return err return err
} }
+1 -4
View File
@@ -22,7 +22,6 @@ import (
type removeOptions struct { type removeOptions struct {
noReset bool noReset bool
yes bool yes bool
context string
} }
func NewRmCommand() *cobra.Command { func NewRmCommand() *cobra.Command {
@@ -39,8 +38,6 @@ func NewRmCommand() *cobra.Command {
}, },
} }
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false, cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
"Do not prompt for confirmation before removing the machine.") "Do not prompt for confirmation before removing the machine.")
cmd.Flags().BoolVar(&opts.noReset, "no-reset", false, cmd.Flags().BoolVar(&opts.noReset, "no-reset", false,
@@ -51,7 +48,7 @@ func NewRmCommand() *cobra.Command {
func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOptions) error { func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOptions) error {
// TODO: automatically choose a connection to the machine that is not being removed. // TODO: automatically choose a connection to the machine that is not being removed.
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -6
View File
@@ -13,7 +13,6 @@ import (
type updateOptions struct { type updateOptions struct {
name string name string
publicIP string publicIP string
context string
} }
func NewUpdateCommand() *cobra.Command { func NewUpdateCommand() *cobra.Command {
@@ -43,10 +42,6 @@ At least one flag must be specified to perform an update operation.`,
&opts.publicIP, "public-ip", "", &opts.publicIP, "public-ip", "",
fmt.Sprintf("Public IP address of the machine for ingress configuration. Use '%s' or '' to remove the public IP.", PublicIPNone), fmt.Sprintf("Public IP address of the machine for ingress configuration. Use '%s' or '' to remove the public IP.", PublicIPNone),
) )
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
@@ -57,7 +52,7 @@ func update(ctx context.Context, uncli *cli.CLI, cmd *cobra.Command, opts update
return fmt.Errorf("at least one update flag must be specified (--name, --public-ip)") return fmt.Errorf("at least one update flag must be specified (--name, --public-ip)")
} }
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return err return err
} }
+5 -2
View File
@@ -24,6 +24,7 @@ import (
type globalOptions struct { type globalOptions struct {
configPath string configPath string
connect string connect string
context string
} }
func main() { func main() {
@@ -39,6 +40,7 @@ func main() {
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT") cli.BindEnvToFlag(cmd, "connect", "UNCLOUD_CONNECT")
cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG") cli.BindEnvToFlag(cmd, "uncloud-config", "UNCLOUD_CONFIG")
cli.BindEnvToFlag(cmd, "context", "UNCLOUD_CONTEXT")
var conn *config.MachineConnection var conn *config.MachineConnection
if opts.connect != "" { if opts.connect != "" {
@@ -67,7 +69,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, opts.context)
if err != nil { if err != nil {
return fmt.Errorf("initialise CLI: %w", err) return fmt.Errorf("initialise CLI: %w", err)
} }
@@ -82,7 +84,8 @@ func main() {
cmd.PersistentFlags().StringVar(&opts.configPath, "uncloud-config", "~/.config/uncloud/config.yaml", cmd.PersistentFlags().StringVar(&opts.configPath, "uncloud-config", "~/.config/uncloud/config.yaml",
"Path to the Uncloud configuration file. [$UNCLOUD_CONFIG]") "Path to the Uncloud configuration file. [$UNCLOUD_CONFIG]")
_ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml") _ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml")
// TODO: make --context a global flag and pass it as a value of the command context. cmd.PersistentFlags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context to use (default is the current context). [$UNCLOUD_CONTEXT]")
cmd.AddCommand( cmd.AddCommand(
NewDeployCommand(), NewDeployCommand(),
+1 -5
View File
@@ -15,7 +15,6 @@ type execCliOptions struct {
detach bool detach bool
interactive bool interactive bool
noTty bool noTty bool
context string
containerId string containerId string
} }
@@ -69,9 +68,6 @@ If the service has multiple replicas and no container ID is specified, the comma
execCmd.Flags().BoolP("tty", "t", false, "Allocate a pseudo-TTY") execCmd.Flags().BoolP("tty", "t", false, "Allocate a pseudo-TTY")
execCmd.Flags().MarkHidden("tty") execCmd.Flags().MarkHidden("tty")
execCmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
// Common flags // Common flags
execCmd.Flags().StringVar(&opts.containerId, "container", "", execCmd.Flags().StringVar(&opts.containerId, "container", "",
"ID of the container to exec into. Accepts full ID or a unique prefix "+ "ID of the container to exec into. Accepts full ID or a unique prefix "+
@@ -95,7 +91,7 @@ func runExec(ctx context.Context, uncli *cli.CLI, serviceName string, command []
} }
} }
client, err := uncli.ConnectClusterWithOptions(ctx, opts.context, cli.ConnectOptions{ client, err := uncli.ConnectClusterWithOptions(ctx, cli.ConnectOptions{
ShowProgress: false, ShowProgress: false,
}) })
if err != nil { if err != nil {
+1 -6
View File
@@ -15,7 +15,6 @@ import (
type inspectOptions struct { type inspectOptions struct {
service string service string
context string
} }
func NewInspectCommand() *cobra.Command { func NewInspectCommand() *cobra.Command {
@@ -30,15 +29,11 @@ func NewInspectCommand() *cobra.Command {
return inspect(cmd.Context(), uncli, opts) return inspect(cmd.Context(), uncli, opts)
}, },
} }
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error { func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+3 -8
View File
@@ -12,25 +12,20 @@ import (
) )
func NewListCommand() *cobra.Command { func NewListCommand() *cobra.Command {
var contextName string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "ls", Use: "ls",
Aliases: []string{"list"}, Aliases: []string{"list"},
Short: "List services.", Short: "List services.",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI) uncli := cmd.Context().Value("cli").(*cli.CLI)
return list(cmd.Context(), uncli, contextName) return list(cmd.Context(), uncli)
}, },
} }
cmd.Flags().StringVarP(
&contextName, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
func list(ctx context.Context, uncli *cli.CLI, contextName string) error { func list(ctx context.Context, uncli *cli.CLI) error {
client, err := uncli.ConnectCluster(ctx, contextName) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -6
View File
@@ -11,7 +11,6 @@ import (
type rmOptions struct { type rmOptions struct {
services []string services []string
context string
} }
func NewRmCommand() *cobra.Command { func NewRmCommand() *cobra.Command {
@@ -27,15 +26,11 @@ func NewRmCommand() *cobra.Command {
return rm(cmd.Context(), uncli, opts) return rm(cmd.Context(), uncli, opts)
}, },
} }
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
func rm(ctx context.Context, uncli *cli.CLI, opts rmOptions) error { func rm(ctx context.Context, uncli *cli.CLI, opts rmOptions) error {
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -8
View File
@@ -34,8 +34,6 @@ type runOptions struct {
replicas uint replicas uint
user string user string
volumes []string volumes []string
context string
} }
func NewRunCommand() *cobra.Command { func NewRunCommand() *cobra.Command {
@@ -112,11 +110,6 @@ func NewRunCommand() *cobra.Command {
" -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+ " -v /data/uploads:/app/uploads Bind mount /data/uploads host directory to /app/uploads in container\n"+
" -v /host/path:/container/path:ro Bind mount a host directory or file as read-only") " -v /host/path:/container/path:ro Bind mount a host directory or file as read-only")
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context to run the service in. (default is the current context)",
)
return cmd return cmd
} }
@@ -126,7 +119,7 @@ func run(ctx context.Context, uncli *cli.CLI, opts runOptions) error {
return err return err
} }
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -7
View File
@@ -15,7 +15,6 @@ import (
type scaleOptions struct { type scaleOptions struct {
service string service string
replicas uint replicas uint
context string
} }
func NewScaleCommand() *cobra.Command { func NewScaleCommand() *cobra.Command {
@@ -39,11 +38,6 @@ func NewScaleCommand() *cobra.Command {
}, },
} }
cmd.Flags().StringVarP(
&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)",
)
return cmd return cmd
} }
@@ -57,7 +51,7 @@ func scale(ctx context.Context, uncli *cli.CLI, opts scaleOptions) error {
) )
} }
clusterClient, err := uncli.ConnectCluster(ctx, opts.context) clusterClient, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -4
View File
@@ -19,7 +19,6 @@ type createOptions struct {
driverOpts []string driverOpts []string
labels []string labels []string
machine string machine string
context string
} }
func NewCreateCommand() *cobra.Command { func NewCreateCommand() *cobra.Command {
@@ -50,14 +49,12 @@ func NewCreateCommand() *cobra.Command {
"Labels to assign to the volume in the form of 'key=value' pairs. Can be specified multiple times.") "Labels to assign to the volume in the form of 'key=value' pairs. Can be specified multiple times.")
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "", cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
"Name or ID of the machine to create the volume on.") "Name or ID of the machine to create the volume on.")
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
return cmd return cmd
} }
func create(ctx context.Context, uncli *cli.CLI, name string, opts createOptions) error { func create(ctx context.Context, uncli *cli.CLI, name string, opts createOptions) error {
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -4
View File
@@ -13,7 +13,6 @@ import (
type inspectOptions struct { type inspectOptions struct {
machine string machine string
context string
} }
func NewInspectCommand() *cobra.Command { func NewInspectCommand() *cobra.Command {
@@ -32,14 +31,12 @@ func NewInspectCommand() *cobra.Command {
cmd.Flags().StringVarP(&opts.machine, "machine", "m", "", cmd.Flags().StringVarP(&opts.machine, "machine", "m", "",
"Name or ID of the machine where the volume is located. "+ "Name or ID of the machine where the volume is located. "+
"If not specified, the volume will be searched across all machines.") "If not specified, the volume will be searched across all machines.")
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
return cmd return cmd
} }
func inspect(ctx context.Context, uncli *cli.CLI, name string, opts inspectOptions) error { func inspect(ctx context.Context, uncli *cli.CLI, name string, opts inspectOptions) error {
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -5
View File
@@ -16,7 +16,6 @@ import (
type listOptions struct { type listOptions struct {
machines []string machines []string
quiet bool quiet bool
context string
} }
func NewListCommand() *cobra.Command { func NewListCommand() *cobra.Command {
@@ -38,14 +37,11 @@ func NewListCommand() *cobra.Command {
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false,
"Only display volume names.") "Only display volume names.")
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
return cmd return cmd
} }
func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error { func list(ctx context.Context, uncli *cli.CLI, opts listOptions) error {
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -5
View File
@@ -14,7 +14,6 @@ type removeOptions struct {
force bool force bool
machines []string machines []string
yes bool yes bool
context string
} }
func NewRemoveCommand() *cobra.Command { func NewRemoveCommand() *cobra.Command {
@@ -41,14 +40,11 @@ func NewRemoveCommand() *cobra.Command {
cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false, cmd.Flags().BoolVarP(&opts.yes, "yes", "y", false,
"Do not prompt for confirmation before removing the volume(s).") "Do not prompt for confirmation before removing the volume(s).")
cmd.Flags().StringVarP(&opts.context, "context", "c", "",
"Name of the cluster context. (default is the current context)")
return cmd return cmd
} }
func remove(ctx context.Context, uncli *cli.CLI, names []string, opts removeOptions) error { func remove(ctx context.Context, uncli *cli.CLI, names []string, opts removeOptions) error {
client, err := uncli.ConnectCluster(ctx, opts.context) client, err := uncli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+1 -3
View File
@@ -40,8 +40,6 @@ type BuildServicesOptions struct {
PushRegistry bool PushRegistry bool
// Cluster-specific options (only used if PushCluster is true). // Cluster-specific options (only used if PushCluster is true).
// Context is the name of the cluster context.
Context string
// Machines is a list of machine names or IDs to push the image to. If empty, images are pushed to all machines. // Machines is a list of machine names or IDs to push the image to. If empty, images are pushed to all machines.
Machines []string Machines []string
} }
@@ -95,7 +93,7 @@ func (cli *CLI) BuildServices(ctx context.Context, project *composetypes.Project
// Add a line break after the build output. // Add a line break after the build output.
fmt.Fprintln(cli.ProgressOut()) fmt.Fprintln(cli.ProgressOut())
clusterClient, err := cli.ConnectCluster(ctx, opts.Context) clusterClient, err := cli.ConnectCluster(ctx)
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
+17 -15
View File
@@ -28,14 +28,16 @@ const (
) )
type CLI struct { type CLI struct {
Config *config.Config Config *config.Config
conn *config.MachineConnection conn *config.MachineConnection
contextOverride string
} }
// New creates a new CLI instance with the given config path or remote machine connection. // New creates a new CLI instance with the given config path or remote machine connection.
// If the connection is provided, the config is ignored for all operations which is useful for interacting with // If the connection is provided, the config is ignored for all operations which is useful for interacting with
// a cluster without creating a config. // a cluster without creating a config.
func New(configPath string, conn *config.MachineConnection) (*CLI, error) { // If a non-empty context name is given, it will override the current default.
func New(configPath string, conn *config.MachineConnection, contextName string) (*CLI, error) {
if conn != nil { if conn != nil {
return &CLI{conn: conn}, nil return &CLI{conn: conn}, nil
} }
@@ -46,7 +48,8 @@ func New(configPath string, conn *config.MachineConnection) (*CLI, error) {
} }
return &CLI{ return &CLI{
Config: cfg, Config: cfg,
contextOverride: contextName,
}, nil }, nil
} }
@@ -68,19 +71,20 @@ func (cli *CLI) SetCurrentContext(name string) error {
return cli.Config.Save() return cli.Config.Save()
} }
// ConnectCluster connects to a cluster using the given context name or the current context if not specified. // ConnectCluster connects to a cluster using the context override 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, contextName string) (*client.Client, error) { func (cli *CLI) ConnectCluster(ctx context.Context) (*client.Client, error) {
return cli.ConnectClusterWithOptions(ctx, contextName, ConnectOptions{ return cli.ConnectClusterWithOptions(ctx, ConnectOptions{
// Default to showing progress for CLI usage. // Default to showing progress for CLI usage.
ShowProgress: true, ShowProgress: true,
}) })
} }
// ConnectClusterWithOptions connects to a cluster using the given context name and options. // ConnectClusterWithOptions connects to a cluster with the given options.
// 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.
// If the CLI has an override context, it is used instead of the current default.
// Options are useful when using the CLI as a library where you may want to disable visual feedback. // Options are useful when using the CLI as a library where you may want to disable visual feedback.
func (cli *CLI) ConnectClusterWithOptions(ctx context.Context, contextName string, opts ConnectOptions) (*client.Client, error) { func (cli *CLI) ConnectClusterWithOptions(ctx context.Context, opts ConnectOptions) (*client.Client, error) {
if cli.conn != nil { if cli.conn != nil {
return ConnectCluster(ctx, *cli.conn, opts) return ConnectCluster(ctx, *cli.conn, opts)
} }
@@ -92,6 +96,8 @@ func (cli *CLI) ConnectClusterWithOptions(ctx context.Context, contextName strin
cli.Config.Path(), cli.Config.Path(),
) )
} }
contextName := cli.contextOverride
if contextName == "" { 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.CurrentContext == "" { if cli.Config.CurrentContext == "" {
@@ -269,7 +275,6 @@ func (cli *CLI) newContextName(name string) (string, error) {
} }
type AddMachineOptions struct { type AddMachineOptions struct {
Context string
MachineName string MachineName string
PublicIP *netip.Addr PublicIP *netip.Addr
RemoteMachine *RemoteMachine RemoteMachine *RemoteMachine
@@ -282,11 +287,11 @@ type AddMachineOptions struct {
// cluster. The machine client is connected to the new machine and can be used to interact with it. // cluster. The machine client is connected to the new machine and can be used to interact with it.
// Both client should be closed after use by the caller. // Both client should be closed after use by the caller.
func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client.Client, *client.Client, error) { func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client.Client, *client.Client, error) {
contextName := opts.Context contextName := cli.contextOverride
if contextName == "" { if contextName == "" {
contextName = cli.Config.CurrentContext contextName = cli.Config.CurrentContext
} }
c, err := cli.ConnectCluster(ctx, contextName) c, err := cli.ConnectCluster(ctx)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err) return nil, nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err)
} }
@@ -407,9 +412,6 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
} else { } else {
connCfg.SSH = config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port) connCfg.SSH = config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port)
} }
if contextName == "" {
contextName = cli.Config.CurrentContext
}
cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg) cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg)
if err = cli.Config.Save(); err != nil { if err = cli.Config.Save(); err != nil {
return nil, nil, fmt.Errorf("save config: %w", err) return nil, nil, fmt.Errorf("save config: %w", err)