github.com/jlmeeker/kismatic@v1.10.1-0.20180612190640-57f9005a1f1a/pkg/cli/validate.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"path/filepath"
     7  
     8  	"os"
     9  
    10  	"github.com/apprenda/kismatic/pkg/install"
    11  	"github.com/apprenda/kismatic/pkg/util"
    12  	"github.com/spf13/cobra"
    13  )
    14  
    15  type validateOpts struct {
    16  	generatedAssetsDir string
    17  	planFile           string
    18  	verbose            bool
    19  	outputFormat       string
    20  	skipPreFlight      bool
    21  	limit              []string
    22  }
    23  
    24  // NewCmdValidate creates a new install validate command
    25  func NewCmdValidate(out io.Writer, installOpts *installOpts) *cobra.Command {
    26  	opts := &validateOpts{}
    27  	cmd := &cobra.Command{
    28  		Use:   "validate",
    29  		Short: "validate your plan file",
    30  		RunE: func(cmd *cobra.Command, args []string) error {
    31  			if len(args) != 0 {
    32  				return fmt.Errorf("Unexpected args: %v", args)
    33  			}
    34  			planner := &install.FilePlanner{File: installOpts.planFilename}
    35  			opts.planFile = installOpts.planFilename
    36  			return doValidate(out, planner, opts)
    37  		},
    38  	}
    39  	cmd.Flags().StringSliceVar(&opts.limit, "limit", []string{}, "comma-separated list of hostnames to limit the execution to a subset of nodes")
    40  	cmd.Flags().StringVar(&opts.generatedAssetsDir, "generated-assets-dir", "generated", "path to the directory where assets generated during the installation process will be stored")
    41  	cmd.Flags().BoolVar(&opts.verbose, "verbose", false, "enable verbose logging from the installation")
    42  	cmd.Flags().StringVarP(&opts.outputFormat, "output", "o", "simple", "installation output format (options simple|raw)")
    43  	cmd.Flags().BoolVar(&opts.skipPreFlight, "skip-preflight", false, "skip pre-flight checks")
    44  	return cmd
    45  }
    46  
    47  func doValidate(out io.Writer, planner install.Planner, opts *validateOpts) error {
    48  	util.PrintHeader(out, "Validating", '=')
    49  	// Check if plan file exists
    50  	if !planner.PlanExists() {
    51  		util.PrettyPrintErr(out, "Reading installation plan file [ERROR]")
    52  		fmt.Fprintln(out, "Run \"kismatic install plan\" to generate it")
    53  		return fmt.Errorf("plan does not exist")
    54  	}
    55  	plan, err := planner.Read()
    56  	if err != nil {
    57  		util.PrettyPrintErr(out, "Reading installation plan file %q", opts.planFile)
    58  		return fmt.Errorf("error reading plan file: %v", err)
    59  	}
    60  	for _, host := range opts.limit {
    61  		if !plan.HostExists(host) {
    62  			return fmt.Errorf("host %q in '--limit' option does not match any hosts in the plan file", host)
    63  		}
    64  	}
    65  	util.PrettyPrintOk(out, "Reading installation plan file %q", opts.planFile)
    66  
    67  	// Validate plan file
    68  	if err := validatePlan(out, plan); err != nil {
    69  		return err
    70  	}
    71  
    72  	// Validate SSH connections
    73  	if err := validateSSHConnectivity(out, plan); err != nil {
    74  		return err
    75  	}
    76  
    77  	// get a new pki
    78  	pki, err := newPKI(out, opts)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	// Validate Certificates
    83  	ok, errs := install.ValidateCertificates(plan, pki)
    84  	if !ok {
    85  		util.PrettyPrintErr(out, "Validating cluster certificates")
    86  		util.PrintValidationErrors(out, errs)
    87  		return fmt.Errorf("Cluster certificates validation error prevents installation from proceeding")
    88  	}
    89  
    90  	if opts.skipPreFlight {
    91  		return nil
    92  	}
    93  	// Run pre-flight
    94  	options := install.ExecutorOptions{
    95  		OutputFormat: opts.outputFormat,
    96  		Verbose:      opts.verbose,
    97  	}
    98  	e, err := install.NewPreFlightExecutor(out, os.Stderr, options)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	return e.RunPreFlightCheck(plan, opts.limit...)
   103  }
   104  
   105  // TODO this should really not be here
   106  func newPKI(stdout io.Writer, options *validateOpts) (*install.LocalPKI, error) {
   107  	ansibleDir := "ansible"
   108  	if options.generatedAssetsDir == "" {
   109  		return nil, fmt.Errorf("GeneratedAssetsDirectory option cannot be empty")
   110  	}
   111  	certsDir := filepath.Join(options.generatedAssetsDir, "keys")
   112  	pki := &install.LocalPKI{
   113  		CACsr: filepath.Join(ansibleDir, "playbooks", "tls", "ca-csr.json"),
   114  		GeneratedCertsDirectory: certsDir,
   115  		Log: stdout,
   116  	}
   117  	return pki, nil
   118  }
   119  
   120  func validatePlan(out io.Writer, plan *install.Plan) error {
   121  	ok, errs := install.ValidatePlan(plan)
   122  	if !ok {
   123  		util.PrettyPrintErr(out, "Validating installation plan file")
   124  		util.PrintValidationErrors(out, errs)
   125  		return fmt.Errorf("Plan file validation error prevents installation from proceeding")
   126  	}
   127  	util.PrettyPrintOk(out, "Validating installation plan file")
   128  	return nil
   129  }
   130  
   131  func validateSSHConnectivity(out io.Writer, plan *install.Plan) error {
   132  	ok, errs := install.ValidatePlanSSHConnections(plan)
   133  	if !ok {
   134  		util.PrettyPrintErr(out, "Validating SSH connectivity to nodes")
   135  		util.PrintValidationErrors(out, errs)
   136  		return fmt.Errorf("SSH connectivity validation error prevents installation from proceeding")
   137  	}
   138  	util.PrettyPrintOk(out, "Validating SSH connectivity to nodes")
   139  	return nil
   140  }