pkg/cmd/serviceaccount/phases/workflow/runner.go (136 lines of code) (raw):
package workflow
import (
"context"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"monis.app/mlog"
)
// RunData contains the data that is passed to the phases
type RunData = interface{}
// Runner is the interface for running phases
type Runner interface {
// AppendPhases adds a number of phases to the list of phases to run
AppendPhases(phases ...Phase)
// AppendSkipPhases adds a number of phases to the list of phases to skip
AppendSkipPhases(phases ...Phase)
// IsPhaseActive returns true if the phase is active
IsPhaseActive(phase Phase) bool
// BindToCommand alters the command's help text and flags to include the phase's flags
BindToCommand(cmd *cobra.Command, data RunData)
// Run runs the phases except the ones specified in skipPhases
Run(data RunData) error
}
// runner is the default implementation of the Runner interface
type runner struct {
skipPhases []string
phases []Phase
}
var _ Runner = &runner{}
// NewRunner returns a new instance of the runner
func NewPhaseRunner() Runner {
return &runner{}
}
// AppendPhases adds a number of phases to the list of phases to run
func (r *runner) AppendPhases(phases ...Phase) {
r.phases = append(r.phases, phases...)
}
// AppendSkipPhases adds a number of phases to the list of phases to skip
func (r *runner) AppendSkipPhases(phases ...Phase) {
for _, phase := range phases {
r.skipPhases = append(r.skipPhases, phase.Name)
}
}
// IsPhaseActive returns true if the phase is active
func (r *runner) IsPhaseActive(phase Phase) bool {
for _, skip := range r.skipPhases {
if skip == phase.Name {
return false
}
}
return true
}
// BindToCommand alters the command's help text and flags to include the phase's flags
func (r *runner) BindToCommand(cmd *cobra.Command, data RunData) {
// Alter the command's help text
if cmd.Short == "" {
cmd.Short = fmt.Sprintf("%s a workload identity", cmd.Use)
}
if cmd.Long == "" {
long := fmt.Sprintf("The \"%s\" command executes the following phases in order:", cmd.Use)
// Add extra padding to align the phase names
longest := 0
for _, phase := range r.phases {
if longest < len(phase.Name) {
longest = len(phase.Name)
}
}
for _, phase := range r.phases {
paddingCount := longest - len(phase.Name)
long += fmt.Sprintf("\n%s%s %s", phase.Name, strings.Repeat(" ", paddingCount), phase.Description)
}
cmd.Long = long
}
// common flags between commands
cmd.Flags().StringSliceVar(&r.skipPhases, "skip-phases", []string{}, "List of phases to skip")
// add the phase command, enabling the user to specify the phase to run
phaseCmd := &cobra.Command{
Use: "phase",
Short: fmt.Sprintf("The \"phase\" command invokes a single phase of the %s workflow", cmd.Use),
}
for _, phase := range r.phases {
// workaround: create a copy of the variable 'phase' so that each subcommand
// gets its own 'phase' variable instead of sharing the iterator variable
p := phase
subcommand := &cobra.Command{
Use: p.Name,
Aliases: p.Aliases,
Short: p.Description,
RunE: func(c *cobra.Command, args []string) error {
// only run this particular phase
r.phases = []Phase{p}
return r.Run(data)
},
}
inheritsFlags(cmd.Flags(), subcommand.Flags(), p.Flags)
phaseCmd.AddCommand(subcommand)
}
cmd.AddCommand(phaseCmd)
}
// Run runs the phases except the ones specified in skipPhases
func (r *runner) Run(data RunData) error {
skipPhases, err := r.computeSkipPhases()
if err != nil {
return errors.Wrap(err, "failed to compute skip phases")
}
filtered := []Phase{}
for _, phase := range r.phases {
if skipPhases[phase.Name] {
mlog.WithName(phase.Name).Info("skipping phase")
continue
}
filtered = append(filtered, phase)
}
// Run PreRun for all phases before executing the phases
for _, phase := range filtered {
if err := phase.PreRun(data); err != nil {
return errors.Wrapf(err, "failed to run pre-run for phase %s", phase.Name)
}
}
for _, phase := range filtered {
if err := phase.Run(context.Background(), data); err != nil {
return errors.Wrapf(err, "failed to run phase %s", phase.Name)
}
}
return nil
}
// computeSkipPhases computes the list of phases to skip based on the skip-phases flag
func (r *runner) computeSkipPhases() (map[string]bool, error) {
currentPhases := make(map[string]bool)
for _, phase := range r.phases {
currentPhases[phase.Name] = true
}
skipPhases := make(map[string]bool)
for _, p := range r.skipPhases {
// check if the phases specified in skip-phases are valid
if _, ok := currentPhases[p]; !ok {
validPhases := make([]string, 0, len(currentPhases))
for _, pp := range r.phases {
validPhases = append(validPhases, pp.Name)
}
return nil, errors.Errorf("phase '%s' not found. Valid phases are: %v", p, validPhases)
}
skipPhases[p] = true
}
return skipPhases, nil
}
// inheritFlags copies flags from the parent command to the child command.
// xref: https://github.com/kubernetes/kubernetes/blob/1f9d448283a7915df9d617708468f06ba17aaaa7/cmd/kubeadm/app/cmd/phases/workflow/runner.go#L400-L414
func inheritsFlags(sourceFlags, targetFlags *pflag.FlagSet, cmdFlags []string) {
// If the list of flag to be inherited from the parent command is not defined, no flag is added
if cmdFlags == nil {
return
}
// add all the flags to be inherited to the target flagSet
sourceFlags.VisitAll(func(f *pflag.Flag) {
for _, c := range cmdFlags {
if f.Name == c {
targetFlags.AddFlag(f)
}
}
})
}