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 }