istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/cmd/mesh/manifest-diff.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mesh 16 17 import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 22 "github.com/spf13/cobra" 23 24 "istio.io/istio/operator/pkg/compare" 25 "istio.io/istio/operator/pkg/util" 26 ) 27 28 // YAMLSuffix is the suffix of a YAML file. 29 const YAMLSuffix = ".yaml" 30 31 type manifestDiffArgs struct { 32 // compareDir indicates comparison between directory. 33 compareDir bool 34 // verbose generates verbose output. 35 verbose bool 36 // selectResources constrains the list of resources to compare to only the ones in this list, ignoring all others. 37 // The format of each list item is :: and the items are comma separated. The * character represents wildcard selection. 38 // e.g. 39 // Deployment:istio-system:* - compare all deployments in istio-system namespace 40 // Service:*:istio-pilot - compare Services called "istio-pilot" in all namespaces. 41 selectResources string 42 // ignoreResources ignores all listed items during comparison. It uses the same list format as selectResources. 43 ignoreResources string 44 // renameResources identifies renamed resources before comparison. 45 // The format of each renaming pair is A->B, all renaming pairs are comma separated. 46 // e.g. Service:*:istio-pilot->Service:*:istio-control - rename istio-pilot service into istio-control 47 renameResources string 48 } 49 50 func addManifestDiffFlags(cmd *cobra.Command, diffArgs *manifestDiffArgs) { 51 cmd.PersistentFlags().BoolVarP(&diffArgs.compareDir, "directory", "r", 52 false, "Compare directory.") 53 cmd.PersistentFlags().BoolVarP(&diffArgs.verbose, "verbose", "v", 54 false, "Verbose output.") 55 cmd.PersistentFlags().StringVar(&diffArgs.selectResources, "select", "::", 56 "Constrain the list of resources to compare to only the ones in this list, ignoring all others.\n"+ 57 "The format of each list item is \"::\" and the items are comma separated. The \"*\" character represents wildcard selection.\n"+ 58 "e.g.\n"+ 59 " Deployment:istio-system:* - compare all deployments in istio-system namespace\n"+ 60 " Service:*:istiod - compare Services called \"istiod\" in all namespaces") 61 cmd.PersistentFlags().StringVar(&diffArgs.ignoreResources, "ignore", "", 62 "Ignore all listed items during comparison, using the same list format as selectResources.") 63 cmd.PersistentFlags().StringVar(&diffArgs.renameResources, "rename", "", 64 "Rename resources before comparison.\n"+ 65 "The format of each renaming pair is A->B, all renaming pairs are comma separated.\n"+ 66 "e.g. Service:*:istiod->Service:*:istio-control - rename istiod service into istio-control") 67 } 68 69 func manifestDiffCmd(diffArgs *manifestDiffArgs) *cobra.Command { 70 cmd := &cobra.Command{ 71 Use: "diff <file|dir> <file|dir>", 72 Short: "Compare manifests and generate diff", 73 Long: "The diff subcommand compares manifests from two files or directories. The output is a list of\n" + 74 "changed paths with the value changes shown as OLD-VALUE -> NEW-VALUE.\n" + 75 "List order changes are shown as [OLD-INDEX->NEW-INDEX], with ? used where a list item is added or\n" + 76 "removed.", 77 Args: func(cmd *cobra.Command, args []string) error { 78 if len(args) != 2 { 79 return fmt.Errorf("diff requires two files or directories") 80 } 81 return nil 82 }, 83 RunE: func(cmd *cobra.Command, args []string) error { 84 var err error 85 var equal bool 86 if diffArgs.compareDir { 87 equal, err = compareManifestsFromDirs(diffArgs.verbose, args[0], args[1], 88 diffArgs.renameResources, diffArgs.selectResources, diffArgs.ignoreResources) 89 if err != nil { 90 return err 91 } 92 if !equal { 93 os.Exit(1) 94 } 95 return nil 96 } 97 98 equal, err = compareManifestsFromFiles(args, diffArgs.verbose, 99 diffArgs.renameResources, diffArgs.selectResources, diffArgs.ignoreResources) 100 if err != nil { 101 return err 102 } 103 if !equal { 104 os.Exit(1) 105 } 106 return nil 107 }, 108 } 109 return cmd 110 } 111 112 // compareManifestsFromFiles compares two manifest files 113 func compareManifestsFromFiles(args []string, verbose bool, 114 renameResources, selectResources, ignoreResources string, 115 ) (bool, error) { 116 a, err := os.ReadFile(args[0]) 117 if err != nil { 118 return false, fmt.Errorf("could not read %q: %v", args[0], err) 119 } 120 b, err := os.ReadFile(args[1]) 121 if err != nil { 122 return false, fmt.Errorf("could not read %q: %v", args[1], err) 123 } 124 125 diff, err := compare.ManifestDiffWithRenameSelectIgnore(string(a), string(b), renameResources, selectResources, 126 ignoreResources, verbose) 127 if err != nil { 128 return false, err 129 } 130 if diff != "" { 131 fmt.Printf("Differences in manifests are:\n%s\n", diff) 132 return false, nil 133 } 134 135 fmt.Println("Manifests are identical") 136 return true, nil 137 } 138 139 func yamlFileFilter(path string) bool { 140 return filepath.Ext(path) == YAMLSuffix 141 } 142 143 // compareManifestsFromDirs compares manifests from two directories 144 func compareManifestsFromDirs(verbose bool, dirName1, dirName2, 145 renameResources, selectResources, ignoreResources string, 146 ) (bool, error) { 147 mf1, err := util.ReadFilesWithFilter(dirName1, yamlFileFilter) 148 if err != nil { 149 return false, err 150 } 151 mf2, err := util.ReadFilesWithFilter(dirName2, yamlFileFilter) 152 if err != nil { 153 return false, err 154 } 155 156 diff, err := compare.ManifestDiffWithRenameSelectIgnore(mf1, mf2, renameResources, selectResources, 157 ignoreResources, verbose) 158 if err != nil { 159 return false, err 160 } 161 if diff != "" { 162 fmt.Printf("Differences in manifests are:\n%s\n", diff) 163 return false, nil 164 } 165 166 fmt.Println("Manifests are identical") 167 return true, nil 168 }