penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/cmd/initcmd/init.go

raw ยท 4547 bytes

package initcmd

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/spf13/cobra"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/klog/v2"

	"undecided.project/monok8s/pkg/bootstrap"
	"undecided.project/monok8s/pkg/config"
)

func NewCmdInit(_ *genericclioptions.ConfigFlags) *cobra.Command {
	var configPath string

	cmd := &cobra.Command{
		Use:   "init [list|STEPSEL]",
		Short: "Start the bootstrap process for this node",
		Long: `Run the node bootstrap process.

STEPSEL allows running specific steps instead of the full sequence.
It supports:

  3           Run step 3
  1-3         Run steps 1 through 3
  -3          Run steps from start through 3
  3-          Run steps from 3 to the end
  1,3,5       Run specific steps
  9-10,15     Combine ranges and individual steps
`,
		Example: `
  ctl init
  ctl init list
  ctl init 1-3
  ctl init -3
  ctl init 3-
  ctl init 1,3,5
  ctl init 9-10,15
`,
		Args: cobra.MaximumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			path, err := (config.Loader{}).ResolvePath(configPath)
			if err != nil {
				return err
			}
			cfg, err := (config.Loader{}).Load(path)
			if err != nil {
				return err
			}

			runner := bootstrap.NewRunner(cfg)

			if len(args) == 1 && strings.EqualFold(strings.TrimSpace(args[0]), "list") {
				steps := runner.InitSteps()

				fmt.Fprintln(cmd.OutOrStdout(), "Showing current bootstrap sequence")

				// width = number of digits of max step number
				width := len(fmt.Sprintf("%d", len(steps)))

				for i, s := range steps {
					fmt.Fprintf(cmd.OutOrStdout(), "\n  %*d. %s\n", width, i+1, s.Name)
					fmt.Fprintf(cmd.OutOrStdout(), "        %s\n", s.Desc)
				}
				return err
			}

			klog.InfoS("starting init", "config", path, "node", cfg.Spec.NodeName)

			if len(args) == 0 {
				return runner.Init(cmd.Context())
			}

			steps := runner.InitSteps()
			sel, err := parseStepSelection(args[0], len(steps))
			if err != nil {
				return err
			}

			klog.InfoS("Running selected init steps", "steps", sel.Indices)
			return runner.InitSelected(cmd.Context(), sel)
		},
	}

	cmd.Flags().StringVarP(&configPath, "config", "c", "", "path to MonoKSConfig yaml")
	return cmd
}

func parseStepSelection(raw string, max int) (bootstrap.StepSelection, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return bootstrap.StepSelection{}, fmt.Errorf("empty step selection")
	}
	if max <= 0 {
		return bootstrap.StepSelection{}, fmt.Errorf("no steps available")
	}

	selected := map[int]struct{}{}

	for _, item := range strings.Split(raw, ",") {
		item = strings.TrimSpace(item)
		if item == "" {
			return bootstrap.StepSelection{}, fmt.Errorf("invalid empty selector in %q", raw)
		}

		// Range or open-ended range
		if strings.Contains(item, "-") {
			if strings.Count(item, "-") != 1 {
				return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", item)
			}

			parts := strings.SplitN(item, "-", 2)
			left := strings.TrimSpace(parts[0])
			right := strings.TrimSpace(parts[1])

			var start, end int

			switch {
			case left == "" && right == "":
				return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", item)

			case left == "":
				n, err := parseStepNumber(right, max)
				if err != nil {
					return bootstrap.StepSelection{}, err
				}
				start, end = 1, n

			case right == "":
				n, err := parseStepNumber(left, max)
				if err != nil {
					return bootstrap.StepSelection{}, err
				}
				start, end = n, max

			default:
				a, err := parseStepNumber(left, max)
				if err != nil {
					return bootstrap.StepSelection{}, err
				}
				b, err := parseStepNumber(right, max)
				if err != nil {
					return bootstrap.StepSelection{}, err
				}
				if a > b {
					return bootstrap.StepSelection{}, fmt.Errorf("invalid descending range %q", item)
				}
				start, end = a, b
			}

			for i := start; i <= end; i++ {
				selected[i] = struct{}{}
			}
			continue
		}

		// Single step
		n, err := parseStepNumber(item, max)
		if err != nil {
			return bootstrap.StepSelection{}, err
		}
		selected[n] = struct{}{}
	}

	indices := make([]int, 0, len(selected))
	for n := range selected {
		indices = append(indices, n)
	}
	sort.Ints(indices)

	return bootstrap.StepSelection{Indices: indices}, nil
}

func parseStepNumber(raw string, max int) (int, error) {
	n, err := strconv.Atoi(strings.TrimSpace(raw))
	if err != nil {
		return 0, fmt.Errorf("invalid step number %q", raw)
	}
	if n < 1 || n > max {
		return 0, fmt.Errorf("step number %d out of range 1-%d", n, max)
	}
	return n, nil
}