github.com/opendevstack/tailor@v1.3.5-0.20220119161809-cab064e60a67/pkg/commands/apply.go (about)

     1  package commands
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/opendevstack/tailor/pkg/cli"
    11  	"github.com/opendevstack/tailor/pkg/openshift"
    12  )
    13  
    14  type printChange func(w io.Writer, change *openshift.Change, revealSecrets bool)
    15  type handleChange func(label string, change *openshift.Change, compareOptions *cli.CompareOptions, ocClient cli.ClientModifier) error
    16  
    17  // Apply prints the drift between desired and current state to STDOUT.
    18  // If there is any, it asks for confirmation and applies the changeset.
    19  func Apply(nonInteractive bool, compareOptions *cli.CompareOptions, ocClient cli.ClientApplier, stdin io.Reader) (bool, error) {
    20  	stdinReader := bufio.NewReader(stdin)
    21  
    22  	var buf bytes.Buffer
    23  	driftDetected, changeset, err := calculateChangeset(&buf, compareOptions, ocClient)
    24  	fmt.Print(buf.String())
    25  	if err != nil {
    26  		return driftDetected, err
    27  	}
    28  
    29  	if driftDetected {
    30  		if nonInteractive {
    31  			err = apply(compareOptions, changeset, ocClient)
    32  			if err != nil {
    33  				return true, fmt.Errorf("Apply aborted: %s", err)
    34  			}
    35  			if compareOptions.Verify {
    36  				err := performVerification(compareOptions, ocClient)
    37  				if err != nil {
    38  					return true, err
    39  				}
    40  			}
    41  			// As apply has run successfully, there should not be any drift
    42  			// anymore. Therefore we report no drift here.
    43  			return false, nil
    44  		}
    45  
    46  		options := []string{"y=yes", "n=no"}
    47  		// Selecting makes no sense when --verify is given, as the verification
    48  		// would fail if not all changes are selected.
    49  		// Selecting is also pointless if there is only one change in total.
    50  		allowSelecting := !compareOptions.Verify && !changeset.ExactlyOne()
    51  		if allowSelecting {
    52  			options = append(options, "s=select")
    53  		}
    54  		a := cli.AskForAction("Apply all changes?", options, stdinReader)
    55  		if a == "y" {
    56  			fmt.Println("")
    57  			err = apply(compareOptions, changeset, ocClient)
    58  			if err != nil {
    59  				return true, fmt.Errorf("Apply aborted: %s", err)
    60  			}
    61  			if compareOptions.Verify {
    62  				err := performVerification(compareOptions, ocClient)
    63  				if err != nil {
    64  					return true, err
    65  				}
    66  			}
    67  			// As apply has run successfully, there should not be any drift
    68  			// anymore. Therefore we report no drift here.
    69  			return false, nil
    70  		} else if allowSelecting && a == "s" {
    71  			anyChangeSkipped := false
    72  
    73  			anyDeleteChangeSkipped, err := askAndApply(compareOptions, ocClient, stdinReader, changeset.Delete, printDeleteChange, "Deleting", ocDelete)
    74  			if err != nil {
    75  				return true, fmt.Errorf("Apply aborted: %s", err)
    76  			} else if anyDeleteChangeSkipped {
    77  				anyChangeSkipped = true
    78  			}
    79  			anyCreateChangeSkipped, err := askAndApply(compareOptions, ocClient, stdinReader, changeset.Create, printCreateChange, "Creating", ocApply)
    80  			if err != nil {
    81  				return true, fmt.Errorf("Apply aborted: %s", err)
    82  			} else if anyCreateChangeSkipped {
    83  				anyChangeSkipped = true
    84  			}
    85  			anyUpdateChangeSkipped, err := askAndApply(compareOptions, ocClient, stdinReader, changeset.Update, printUpdateChange, "Updating", ocApply)
    86  			if err != nil {
    87  				return true, fmt.Errorf("Apply aborted: %s", err)
    88  			} else if anyUpdateChangeSkipped {
    89  				anyChangeSkipped = true
    90  			}
    91  
    92  			return anyChangeSkipped, nil
    93  		}
    94  
    95  		// Changes were not applied, so we report that drift was detected.
    96  		return true, nil
    97  	}
    98  
    99  	// No drift, nothing to do ...
   100  	return false, nil
   101  }
   102  
   103  func askAndApply(compareOptions *cli.CompareOptions, ocClient cli.ClientApplier, stdinReader *bufio.Reader, changes []*openshift.Change, changePrinter printChange, label string, changeHandler handleChange) (bool, error) {
   104  	anyChangeSkipped := false
   105  
   106  	for _, change := range changes {
   107  		fmt.Println("")
   108  		var buf bytes.Buffer
   109  		changePrinter(&buf, change, compareOptions.RevealSecrets)
   110  		fmt.Print(buf.String())
   111  		a := cli.AskForAction(
   112  			fmt.Sprintf("Apply change to %s?", change.ItemName()),
   113  			[]string{"y=yes", "n=no"},
   114  			stdinReader,
   115  		)
   116  		if a == "y" {
   117  			fmt.Println("")
   118  			err := changeHandler(label, change, compareOptions, ocClient)
   119  			if err != nil {
   120  				return true, fmt.Errorf("Apply aborted: %s", err)
   121  			}
   122  		} else {
   123  			anyChangeSkipped = true
   124  		}
   125  	}
   126  	return anyChangeSkipped, nil
   127  }
   128  
   129  func apply(compareOptions *cli.CompareOptions, c *openshift.Changeset, ocClient cli.ClientModifier) error {
   130  
   131  	for _, change := range c.Delete {
   132  		err := ocDelete("Deleting", change, compareOptions, ocClient)
   133  		if err != nil {
   134  			return err
   135  		}
   136  	}
   137  
   138  	for _, change := range c.Create {
   139  		err := ocApply("Creating", change, compareOptions, ocClient)
   140  		if err != nil {
   141  			return err
   142  		}
   143  	}
   144  
   145  	for _, change := range c.Update {
   146  		err := ocApply("Updating", change, compareOptions, ocClient)
   147  		if err != nil {
   148  			return err
   149  		}
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  func ocDelete(label string, change *openshift.Change, compareOptions *cli.CompareOptions, ocClient cli.ClientModifier) error {
   156  	fmt.Printf("%s %s ... ", label, change.ItemName())
   157  	errBytes, err := ocClient.Delete(change.Kind, change.Name)
   158  	if err == nil {
   159  		fmt.Println("done")
   160  	} else {
   161  		fmt.Println("failed")
   162  		return errors.New(string(errBytes))
   163  	}
   164  	return nil
   165  }
   166  
   167  func ocApply(label string, change *openshift.Change, compareOptions *cli.CompareOptions, ocClient cli.ClientModifier) error {
   168  	fmt.Printf("%s %s ... ", label, change.ItemName())
   169  	errBytes, err := ocClient.Apply(change.DesiredState, compareOptions.Selector)
   170  	if err == nil {
   171  		fmt.Println("done")
   172  	} else {
   173  		fmt.Println("failed")
   174  		return errors.New(string(errBytes))
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func performVerification(compareOptions *cli.CompareOptions, ocClient cli.ClientProcessorExporter) error {
   181  	var buf bytes.Buffer
   182  	fmt.Print("\nVerifying current state matches desired state ... ")
   183  	driftDetected, _, err := calculateChangeset(&buf, compareOptions, ocClient)
   184  	if err != nil {
   185  		return fmt.Errorf("Error: %s", err)
   186  	}
   187  	if driftDetected {
   188  		fmt.Print("failed! Detected drift:\n\n")
   189  		fmt.Println(buf.String())
   190  		return errors.New("Verification failed")
   191  	}
   192  	fmt.Println("successful")
   193  	return nil
   194  }