command_pool

package
v0.38.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AcquireCmd = &cli.Command{
	Name:        "acquire",
	Usage:       "Acquire a pool member exclusively",
	Description: "Check out one member of a pool for exclusive use until the lease expires (or is released). Method calls and pool-name port routing skip the member while held; reach it by its own space name or by pinning calls with its space id.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name or ID of the pool",
			Required: true,
		},
	},
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:  "time",
			Usage: "How long to hold the member, e.g. 90s, 5m, 2h; 'none' for a lease that never expires (unlimited pools). Defaults to the pool's configured maximum.",
		},
		&cli.StringFlag{
			Name:  "wait",
			Usage: "How long to wait for a member if none is free yet (e.g. while one is being replaced or starting), e.g. 30s, 2m (max 5m). Defaults to 10s; use --wait 0s to fail immediately.",
		},
		leaseJSONFlag(),
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")

		duration, err := parseLeaseDuration(cmd.GetString("time"))
		if err != nil {
			return err
		}

		wait := 10
		if value := cmd.GetString("wait"); value != "" {
			wait, err = parseLeaseDuration(value)
			if err != nil {
				return fmt.Errorf("invalid --wait: %w", err)
			}
		}

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		if !cmd.GetBool("json") {
			fmt.Printf("Acquiring a member of pool %q...\n", poolName)
		}
		lease, code, err := client.AcquirePoolLease(context.Background(), poolName, &apiclient.PoolLeaseAcquireRequest{
			DurationSeconds: duration,
			WaitSeconds:     wait,
		})
		if err != nil {
			return handleLeaseError(cmd, code, err, fmt.Sprintf("no free member available in pool %q", poolName))
		}

		if cmd.GetBool("json") {
			return printLeaseJSON(lease)
		}
		printLease(lease, "acquired")
		return nil
	},
}
View Source
var CreateCmd = &cli.Command{
	Name:        "create",
	Usage:       "Create a pool",
	Description: "Create a pool of identical spaces from a template. Set --lease-time to enable exclusive member leases (acquire / release).",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name of the new pool",
			Required: true,
		},
		&cli.StringArg{
			Name:     "template",
			Usage:    "The name or ID of the template members are created from",
			Required: true,
		},
	},
	Flags: []cli.Flag{
		&cli.IntFlag{
			Name:         "count",
			Usage:        "The number of spaces to keep in the pool.",
			DefaultValue: 1,
		},
		&cli.BoolFlag{
			Name:  "start",
			Usage: "Start the pool immediately.",
		},
		&cli.StringFlag{
			Name:  "lease-time",
			Usage: "Enable exclusive member leases: the maximum lease length, e.g. 30m, 2h; 'none' for leases that last until released. Leases are disabled when not set.",
		},
		&cli.StringFlag{
			Name:  "lease-extensions",
			Usage: "How many times a lease may be extended: a number, or 'unlimited'. Defaults to 0 (no extensions).",
		},
		&cli.StringSliceFlag{
			Name:  "custom-field",
			Usage: "Custom field given to every member, as name=value (can be specified multiple times).",
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")
		templateName := cmd.GetStringArg("template")

		count := cmd.GetInt("count")
		if count < 1 {
			return fmt.Errorf("count must be at least 1")
		}

		leaseTime, err := parseLeaseDuration(cmd.GetString("lease-time"))
		if err != nil {
			return fmt.Errorf("invalid --lease-time: %w", err)
		}

		leaseExtensions := 0
		switch value := cmd.GetString("lease-extensions"); value {
		case "":
		case "unlimited":
			leaseExtensions = -1
		default:
			leaseExtensions, err = strconv.Atoi(value)
			if err != nil || leaseExtensions < -1 {
				return fmt.Errorf("invalid --lease-extensions %q (use a number or unlimited)", value)
			}
		}
		if leaseTime == 0 && leaseExtensions != 0 {
			return fmt.Errorf("--lease-extensions needs --lease-time to enable leases")
		}

		customFields, err := cmdutil.ParseCustomFields(cmd.GetStringSlice("custom-field"))
		if err != nil {
			return err
		}

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		templateId := templateName
		if template, err := client.GetTemplateByName(context.Background(), templateName); err == nil && template != nil {
			templateId = template.TemplateId
		}

		fmt.Printf("Creating pool %q from template %q...\n", poolName, templateName)

		response, _, err := client.CreatePool(context.Background(), &apiclient.PoolRequest{
			Name:               poolName,
			TemplateId:         templateId,
			DesiredCount:       count,
			Active:             cmd.GetBool("start"),
			LeaseMaxTime:       leaseTime,
			LeaseMaxExtensions: leaseExtensions,
			CustomFields:       customFields,
		})
		if err != nil {
			return fmt.Errorf("couldn't create pool %q: %s", poolName, cmdutil.CleanAPIError(err))
		}

		state := "created stopped"
		if cmd.GetBool("start") {
			state = "started"
		}
		fmt.Printf("Pool %q %s with %d space(s), ID: %s\n", poolName, state, count, response.Id)
		if leaseTime != 0 {
			fmt.Println("Leases enabled: use 'knot pool acquire " + poolName + "' to check out a member.")
		}
		return nil
	},
}
View Source
var DeleteCmd = &cli.Command{
	Name:        "delete",
	Usage:       "Delete a pool",
	Description: "Delete a stopped pool and all its member spaces.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name or ID of the pool to delete",
			Required: true,
		},
	},
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:    "yes",
			Aliases: []string{"y"},
			Usage:   "Skip confirmation prompt.",
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")

		if !cmd.GetBool("yes") {
			fmt.Printf("Delete pool %q and all its spaces? [y/N] ", poolName)
			var resp string
			fmt.Scanln(&resp)
			if resp != "y" && resp != "Y" && resp != "yes" {
				fmt.Println("Cancelled.")
				return nil
			}
		}

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		_, err = client.DeletePool(context.Background(), poolName)
		if err != nil {
			return fmt.Errorf("couldn't delete pool %q: %w", poolName, err)
		}

		fmt.Fprintln(os.Stderr, "Pool deleted:", poolName)
		return nil
	},
}
View Source
var ExtendCmd = &cli.Command{
	Name:        "extend",
	Usage:       "Extend the lease on a pool member",
	Description: "Renew a lease: the new deadline becomes now + the given duration (or never, on unlimited pools). Bounded by the pool's maximum extension count.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The held member's name or space id — the same identifier acquire returned",
			Required: true,
		},
	},
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:  "time",
			Usage: "How long to hold the member from now, e.g. 90s, 5m, 2h; 'none' for no expiry (unlimited pools). Defaults to the pool's configured maximum.",
		},
		leaseJSONFlag(),
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceArg := cmd.GetStringArg("space")

		duration, err := parseLeaseDuration(cmd.GetString("time"))
		if err != nil {
			return err
		}

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		lease, code, err := client.ExtendSpaceLease(context.Background(), spaceArg, &apiclient.PoolLeaseExtendRequest{
			DurationSeconds: duration,
		})
		if err != nil {
			return handleLeaseError(cmd, code, err, fmt.Sprintf("lease on %s cannot be extended (limit reached or already ended)", spaceArg))
		}

		if cmd.GetBool("json") {
			return printLeaseJSON(lease)
		}
		printLease(lease, "extended")
		return nil
	},
}
View Source
var LeasesCmd = &cli.Command{
	Name:        "leases",
	Usage:       "List a pool's leases",
	Description: "Lists the pool's exclusively held leases — active plus draining (ended, waiting for in-flight work to finish).",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name or ID of the pool",
			Required: true,
		},
	},
	Flags: []cli.Flag{
		leaseJSONFlag(),
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		leases, code, err := client.GetPoolLeases(context.Background(), poolName)
		if err != nil {
			return handleLeaseError(cmd, code, err, "")
		}

		if cmd.GetBool("json") {
			enc := json.NewEncoder(os.Stdout)
			enc.SetIndent("", "  ")
			return enc.Encode(leases)
		}

		if len(leases.Leases) == 0 {
			fmt.Printf("No leases held on pool %q.\n", poolName)
			return nil
		}

		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "MEMBER\tSTATE\tEXPIRES\tEXTENSIONS\tHOLDER")
		for _, lease := range leases.Leases {
			fmt.Fprintf(w, "%s\t%s\t%s\t%d/%s\t%s\n",
				lease.SpaceName,
				lease.State,
				formatLeaseExpiry(lease.ExpiresAt),
				lease.ExtensionsUsed,
				formatMaxExtensions(lease.MaxExtensions),
				lease.Username,
			)
		}
		w.Flush()
		return nil
	},
}
View Source
var ListCmd = &cli.Command{
	Name:        "list",
	Usage:       "List your pools",
	Description: "Lists all pools you own with their status and member count.",
	MaxArgs:     cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		pools, code, err := client.GetPools(context.Background())
		if err != nil {
			if code == 404 {
				fmt.Println("No pools found.")
				return nil
			}
			return fmt.Errorf("couldn't list pools: %w", err)
		}

		if len(pools.Pools) == 0 {
			fmt.Println("No pools found.")
			return nil
		}

		templateNames := map[string]string{}
		if templates, _, err := client.GetTemplates(context.Background()); err == nil && templates != nil {
			for _, t := range templates.Templates {
				templateNames[t.Id] = t.Name
			}
		}

		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
		fmt.Fprintln(w, "NAME\tSTATE\tALIVE/DESIRED\tTEMPLATE")
		for _, p := range pools.Pools {
			state := "stopped"
			if p.Active {
				state = "active"
			}
			template := p.TemplateId
			if name, ok := templateNames[p.TemplateId]; ok {
				template = name
			}
			fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", p.Name, state, p.AliveMembers, p.DesiredCount, template)
		}
		w.Flush()
		return nil
	},
}
View Source
var PoolCmd = &cli.Command{
	Name:        "pool",
	Usage:       "Manage space pools",
	Description: "Manage your space pools from the command line.",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:    "server",
			Aliases: []string{"s"},
			Usage:   "The address of the remote server to manage pools on.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_SERVER"},
			Global:  true,
		},
		&cli.StringFlag{
			Name:    "token",
			Aliases: []string{"t"},
			Usage:   "The token to use for authentication.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TOKEN"},
			Global:  true,
		},
		&cli.BoolFlag{
			Name:         "tls-skip-verify",
			Usage:        "Skip TLS verification when talking to server.",
			ConfigPath:   []string{"tls.skip_verify"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"},
			DefaultValue: true,
			Global:       true,
		},
		&cli.StringFlag{
			Name:         "alias",
			Aliases:      []string{"a"},
			Usage:        "The server alias to use.",
			DefaultValue: "default",
			Global:       true,
		},
	},
	Commands: []*cli.Command{
		ListCmd,
		CreateCmd,
		StartCmd,
		StopCmd,
		DeleteCmd,
		SetSizeCmd,
		UpdateCmd,
		AcquireCmd,
		ExtendCmd,
		ReleaseCmd,
		LeasesCmd,
	},
}
View Source
var ReleaseCmd = &cli.Command{
	Name:        "release",
	Usage:       "Release a leased pool member",
	Description: "End the lease held on a pool member. The member returns to the pool once any in-flight work has finished (normally within one sweep, ~15s). With --destroy the member is deleted instead and a fresh replacement is created, so the next acquire gets a clean space.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The held member's name or space id — the same identifier acquire returned",
			Required: true,
		},
	},
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:  "destroy",
			Usage: "Destroy the member instead of returning it — a fresh replacement is created, so the next acquire gets a clean space.",
		},
		leaseJSONFlag(),
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceArg := cmd.GetStringArg("space")
		destroy := cmd.GetBool("destroy")

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		lease, code, err := client.ReleaseSpaceLease(context.Background(), spaceArg, destroy)
		if err != nil {
			return handleLeaseError(cmd, code, err, "")
		}

		if cmd.GetBool("json") {
			return printLeaseJSON(lease)
		}

		if destroy {
			fmt.Printf("Member %s destroyed; a fresh replacement is starting in pool %q.\n", spaceArg, lease.PoolName)
		} else {
			fmt.Printf("Member %s released; it returns to pool %q after in-flight work drains.\n", spaceArg, lease.PoolName)
		}
		return nil
	},
}
View Source
var SetSizeCmd = &cli.Command{
	Name:        "set-size",
	Usage:       "Set the desired space count for a pool",
	Description: "Update the target number of spaces for a pool. The sweep loop handles creating or removing members.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name or ID of the pool",
			Required: true,
		},
		&cli.IntArg{
			Name:     "count",
			Usage:    "The desired number of spaces (minimum 1)",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")
		desired := cmd.GetIntArg("count")

		if desired < 1 {
			return fmt.Errorf("count must be at least 1")
		}

		fmt.Printf("Setting pool %q size to %d...\n", poolName, desired)

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		_, err = client.SetPoolSize(context.Background(), poolName, desired)
		if err != nil {
			return fmt.Errorf("couldn't set the size of pool %q: %w", poolName, err)
		}

		fmt.Printf("Pool %q size set to %d.\n", poolName, desired)
		return nil
	},
}
View Source
var StartCmd = &cli.Command{
	Name:        "start",
	Usage:       "Start a pool",
	Description: "Start a stopped pool and all its member spaces.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name or ID of the pool to start",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")
		fmt.Println("Starting pool:", poolName)

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		_, err = client.StartPool(context.Background(), poolName)
		if err != nil {
			return fmt.Errorf("couldn't start pool %q: %w", poolName, err)
		}

		fmt.Println("Pool started:", poolName)
		return nil
	},
}
View Source
var StopCmd = &cli.Command{
	Name:        "stop",
	Usage:       "Stop a pool",
	Description: "Stop a running pool and all its member spaces without deleting them.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name or ID of the pool to stop",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")
		fmt.Println("Stopping pool:", poolName)

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		_, err = client.StopPool(context.Background(), poolName)
		if err != nil {
			return fmt.Errorf("couldn't stop pool %q: %w", poolName, err)
		}

		fmt.Println("Pool stopped:", poolName)
		return nil
	},
}
View Source
var UpdateCmd = &cli.Command{
	Name:  "update",
	Usage: "Update the custom fields given to pool members",
	Description: `Set or remove the custom field values given to every member of a pool. Members pick up the change the next time they are created or started; restart the pool (stop, then start) to apply it to all of them.

With no flags, shows the pool's current custom field values.`,
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "pool",
			Usage:    "The name or ID of the pool",
			Required: true,
		},
	},
	Flags: []cli.Flag{
		&cli.StringSliceFlag{
			Name:  "custom-field",
			Usage: "Set a custom field as name=value (can be specified multiple times).",
		},
		&cli.StringSliceFlag{
			Name:  "unset-custom-field",
			Usage: "Remove a custom field, so members get the template default (can be specified multiple times).",
		},
		&cli.BoolFlag{
			Name:  "clear-custom-fields",
			Usage: "Remove every custom field before applying --custom-field.",
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		poolName := cmd.GetStringArg("pool")

		set, err := cmdutil.ParseCustomFields(cmd.GetStringSlice("custom-field"))
		if err != nil {
			return err
		}
		unset := cmd.GetStringSlice("unset-custom-field")
		clearAll := cmd.GetBool("clear-custom-fields")

		client, err := cmdutil.GetClient(cmd)
		if err != nil {
			return err
		}

		pool, _, err := client.GetPool(context.Background(), poolName)
		if err != nil {
			return fmt.Errorf("couldn't get pool %q: %s", poolName, cmdutil.CleanAPIError(err))
		}

		masked := map[string]bool{}
		if template, _, err := client.GetTemplate(context.Background(), pool.TemplateId); err == nil && template != nil {
			for _, field := range template.CustomFields {
				if field.Type == "masked" {
					masked[field.Name] = true
				}
			}
		}

		if len(set) == 0 && len(unset) == 0 && !clearAll {
			printPoolCustomFields(pool.CustomFields, masked)
			return nil
		}

		fields := []apiclient.CustomFieldValue{}
		if !clearAll {
			for _, field := range pool.CustomFields {
				if !slices.Contains(unset, field.Name) {
					fields = append(fields, field)
				}
			}
		}
		for _, field := range set {
			i := slices.IndexFunc(fields, func(f apiclient.CustomFieldValue) bool { return f.Name == field.Name })
			if i >= 0 {
				fields[i].Value = field.Value
			} else {
				fields = append(fields, field)
			}
		}

		if _, err := client.PatchPool(context.Background(), poolName, &apiclient.PoolUpdateRequest{CustomFields: &fields}); err != nil {
			return fmt.Errorf("couldn't update pool %q: %s", poolName, cmdutil.CleanAPIError(err))
		}

		fmt.Printf("Pool %q updated; members pick up the change when they are next created or started.\n", poolName)
		printPoolCustomFields(fields, masked)
		return nil
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL