penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/cmd/initcmd/init.go

raw ยท 4076 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",
		Example: strings.TrimSpace(`
  ctl init
  ctl init list
  ctl init 1-3
  ctl init -3
  ctl init 3-
  ctl init 1,3,5
`),
		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
			}

			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{}{}

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

		if strings.Contains(part, "-") {
			if strings.Count(part, "-") != 1 {
				return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", part)
			}

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

			var start, end int
			switch {
			case left == "" && right == "":
				return bootstrap.StepSelection{}, fmt.Errorf("invalid range %q", part)

			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
				}
				start, end = a, b
			}

			if start > end {
				return bootstrap.StepSelection{}, fmt.Errorf("invalid descending range %q", part)
			}

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

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

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

	return bootstrap.StepSelection{Indices: out}, 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
}