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

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"regexp"
    10  
    11  	"github.com/opendevstack/tailor/pkg/cli"
    12  	"github.com/opendevstack/tailor/pkg/openshift"
    13  	"golang.org/x/sync/errgroup"
    14  )
    15  
    16  // Diff prints the drift between desired and current state to STDOUT.
    17  func Diff(compareOptions *cli.CompareOptions) (bool, error) {
    18  	ocClient := cli.NewOcClient(compareOptions.Namespace)
    19  	var buf bytes.Buffer
    20  	driftDetected, _, err := calculateChangeset(&buf, compareOptions, ocClient)
    21  	fmt.Print(buf.String())
    22  	return driftDetected, err
    23  }
    24  
    25  func calculateChangeset(w io.Writer, compareOptions *cli.CompareOptions, ocClient cli.ClientProcessorExporter) (bool, *openshift.Changeset, error) {
    26  	updateRequired := false
    27  
    28  	where := compareOptions.TemplateDir
    29  
    30  	fmt.Fprintf(w,
    31  		"Comparing templates in %s with OCP namespace %s.\n",
    32  		where,
    33  		compareOptions.Namespace,
    34  	)
    35  
    36  	if len(compareOptions.Resource) > 0 && len(compareOptions.Selector) > 0 {
    37  		fmt.Fprintf(w,
    38  			"Limiting resources to %s with selector %s.\n",
    39  			compareOptions.Resource,
    40  			compareOptions.Selector,
    41  		)
    42  	} else if len(compareOptions.Selector) > 0 {
    43  		fmt.Fprintf(w,
    44  			"Limiting to resources with selector %s.\n",
    45  			compareOptions.Selector,
    46  		)
    47  	} else if len(compareOptions.Resource) > 0 {
    48  		fmt.Fprintf(w,
    49  			"Limiting resources to %s.\n",
    50  			compareOptions.Resource,
    51  		)
    52  	}
    53  
    54  	resource := compareOptions.Resource
    55  
    56  	filter, err := openshift.NewResourceFilter(resource, compareOptions.Selector, compareOptions.Excludes)
    57  	if err != nil {
    58  		return updateRequired, &openshift.Changeset{}, err
    59  	}
    60  
    61  	var templateBasedList *openshift.ResourceList
    62  	var platformBasedList *openshift.ResourceList
    63  
    64  	eg := new(errgroup.Group)
    65  	eg.Go(func() error {
    66  		l, err := assembleTemplateBasedResourceList(
    67  			filter,
    68  			compareOptions,
    69  			ocClient,
    70  		)
    71  		if err != nil {
    72  			return err
    73  		}
    74  		templateBasedList = l
    75  		return nil
    76  	})
    77  	eg.Go(func() error {
    78  		l, err := assemblePlatformBasedResourceList(filter, compareOptions, ocClient)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		platformBasedList = l
    83  		return nil
    84  	})
    85  	if err := eg.Wait(); err != nil {
    86  		return updateRequired, &openshift.Changeset{}, err
    87  	}
    88  
    89  	platformResourcesWord := "resources"
    90  	if platformBasedList.Length() == 1 {
    91  		platformResourcesWord = "resource"
    92  	}
    93  	templateResourcesWord := "resources"
    94  	if templateBasedList.Length() == 1 {
    95  		templateResourcesWord = "resource"
    96  	}
    97  	fmt.Fprintf(w,
    98  		"Found %d %s in OCP cluster (current state) and %d %s in processed templates (desired state).\n\n",
    99  		platformBasedList.Length(),
   100  		platformResourcesWord,
   101  		templateBasedList.Length(),
   102  		templateResourcesWord,
   103  	)
   104  
   105  	if templateBasedList.Length() == 0 && !compareOptions.Force {
   106  		fmt.Fprint(w, "No items where found in desired state. ")
   107  		if len(compareOptions.Resource) == 0 && len(compareOptions.Selector) == 0 {
   108  			fmt.Fprintf(w,
   109  				"Are there any templates in %s?\n",
   110  				where,
   111  			)
   112  		} else {
   113  			fmt.Fprintf(w,
   114  				"Possible reasons are:\n"+
   115  					"* No templates are located in %s\n",
   116  				where,
   117  			)
   118  			if len(compareOptions.Resource) > 0 {
   119  				fmt.Fprintf(w,
   120  					"* No templates contain resources of kinds: %s\n",
   121  					compareOptions.Resource,
   122  				)
   123  			}
   124  			if len(compareOptions.Selector) > 0 {
   125  				fmt.Fprintf(w,
   126  					"* No templates contain resources matching selector: %s\n",
   127  					compareOptions.Selector,
   128  				)
   129  			}
   130  		}
   131  		fmt.Fprintln(w, "\nRefusing to continue without --force")
   132  		return updateRequired, &openshift.Changeset{}, errors.New("Diff not performed due to misconfiguration")
   133  	}
   134  
   135  	changeset, err := compare(
   136  		w,
   137  		platformBasedList,
   138  		templateBasedList,
   139  		compareOptions.UpsertOnly,
   140  		compareOptions.AllowRecreate,
   141  		compareOptions.RevealSecrets,
   142  		compareOptions.PathsToPreserve(),
   143  	)
   144  	if err != nil {
   145  		return false, changeset, err
   146  	}
   147  	updateRequired = !changeset.Blank()
   148  	return updateRequired, changeset, nil
   149  }
   150  
   151  func compare(w io.Writer, remoteResourceList *openshift.ResourceList, localResourceList *openshift.ResourceList, upsertOnly bool, allowRecreate bool, revealSecrets bool, preservePaths []string) (*openshift.Changeset, error) {
   152  	changeset, err := openshift.NewChangeset(remoteResourceList, localResourceList, upsertOnly, allowRecreate, preservePaths)
   153  	if err != nil {
   154  		return changeset, err
   155  	}
   156  
   157  	for _, change := range changeset.Noop {
   158  		fmt.Fprintf(w, "* %s is in sync\n", change.ItemName())
   159  	}
   160  
   161  	for _, change := range changeset.Delete {
   162  		printDeleteChange(w, change, revealSecrets)
   163  	}
   164  
   165  	for _, change := range changeset.Create {
   166  		printCreateChange(w, change, revealSecrets)
   167  	}
   168  
   169  	for _, change := range changeset.Update {
   170  		printUpdateChange(w, change, revealSecrets)
   171  	}
   172  
   173  	fmt.Fprintf(w, "\nSummary: %d in sync, ", len(changeset.Noop))
   174  	cli.FprintGreenf(w, "%d to create", len(changeset.Create))
   175  	fmt.Fprint(w, ", ")
   176  	cli.FprintYellowf(w, "%d to update", len(changeset.Update))
   177  	fmt.Fprint(w, ", ")
   178  	cli.FprintRedf(w, "%d to delete\n\n", len(changeset.Delete))
   179  
   180  	return changeset, nil
   181  }
   182  
   183  func printDeleteChange(w io.Writer, change *openshift.Change, revealSecrets bool) {
   184  	cli.FprintRedf(w, "- %s to delete\n", change.ItemName())
   185  	fmt.Fprint(w, change.Diff(revealSecrets))
   186  }
   187  
   188  func printCreateChange(w io.Writer, change *openshift.Change, revealSecrets bool) {
   189  	cli.FprintGreenf(w, "+ %s to create\n", change.ItemName())
   190  	fmt.Fprint(w, change.Diff(revealSecrets))
   191  }
   192  
   193  func printUpdateChange(w io.Writer, change *openshift.Change, revealSecrets bool) {
   194  	cli.FprintYellowf(w, "~ %s to update\n", change.ItemName())
   195  	fmt.Fprint(w, change.Diff(revealSecrets))
   196  }
   197  
   198  func assembleTemplateBasedResourceList(filter *openshift.ResourceFilter, compareOptions *cli.CompareOptions, ocClient cli.OcClientProcessor) (*openshift.ResourceList, error) {
   199  	var inputs [][]byte
   200  
   201  	files, err := ioutil.ReadDir(compareOptions.TemplateDir)
   202  	if err != nil {
   203  		return nil, fmt.Errorf("Cannot get files in template directory '%s': %s", compareOptions.TemplateDir, err)
   204  	}
   205  	filePattern := ".*\\.ya?ml$"
   206  	re := regexp.MustCompile(filePattern)
   207  	for _, file := range files {
   208  		matched := re.MatchString(file.Name())
   209  		if !matched {
   210  			continue
   211  		}
   212  		cli.DebugMsg("Reading template", file.Name())
   213  		processedOut, err := openshift.ProcessTemplate(
   214  			compareOptions.TemplateDir,
   215  			file.Name(),
   216  			compareOptions.ParamDir,
   217  			compareOptions,
   218  			ocClient,
   219  		)
   220  		if err != nil {
   221  			return nil, fmt.Errorf("Could not process %s template: %s", file.Name(), err)
   222  		}
   223  		inputs = append(inputs, processedOut)
   224  	}
   225  
   226  	return openshift.NewTemplateBasedResourceList(filter, inputs...)
   227  }
   228  
   229  func assemblePlatformBasedResourceList(filter *openshift.ResourceFilter, compareOptions *cli.CompareOptions, ocClient cli.OcClientExporter) (*openshift.ResourceList, error) {
   230  	exportedOut, err := ocClient.Export(filter.ConvertToKinds(), filter.Label)
   231  	if err != nil {
   232  		return nil, fmt.Errorf("Could not export %s resources: %s", filter.String(), err)
   233  	}
   234  	return openshift.NewPlatformBasedResourceList(filter, exportedOut)
   235  }