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 }