istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/bug-report/pkg/bugreport/flags.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 bugreport 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "os" 21 "time" 22 23 jsonpatch "github.com/evanphx/json-patch/v5" 24 "github.com/spf13/cobra" 25 "sigs.k8s.io/yaml" 26 27 "istio.io/istio/pkg/kube/inject" 28 "istio.io/istio/pkg/slices" 29 config2 "istio.io/istio/tools/bug-report/pkg/config" 30 ) 31 32 var ( 33 startTime, endTime, configFile, tempDir, outputDir string 34 included, excluded []string 35 commandTimeout, since time.Duration 36 gConfig = &config2.BugReportConfig{} 37 ) 38 39 func addFlags(cmd *cobra.Command, args *config2.BugReportConfig) { 40 // k8s client config 41 cmd.PersistentFlags().StringVarP(&args.KubeConfigPath, "kubeconfig", "c", "", 42 "Path to kube config.") 43 cmd.PersistentFlags().StringVar(&args.Context, "context", "", 44 "Name of the kubeconfig Context to use.") 45 46 // input config 47 cmd.PersistentFlags().StringVarP(&configFile, "filename", "f", "", 48 "Path to a file containing configuration in YAML format. The file contents are applied over the default "+ 49 "values and flag settings, with lists being replaced per JSON merge semantics.") 50 51 // dry run 52 cmd.PersistentFlags().BoolVarP(&args.DryRun, "dry-run", "", false, 53 "Only log commands that would be run, don't fetch or write.") 54 55 // full secrets 56 cmd.PersistentFlags().BoolVarP(&args.FullSecrets, "full-secrets", "", false, 57 "If set, secret contents are included in output.") 58 59 // istio namespaces 60 cmd.PersistentFlags().StringVar(&args.IstioNamespace, "istio-namespace", bugReportDefaultIstioNamespace, 61 "Namespace where Istio control plane is installed.") 62 63 // timeouts and max sizes 64 cmd.PersistentFlags().DurationVar(&commandTimeout, "timeout", bugReportDefaultTimeout, 65 "Maximum amount of time to spend fetching logs. When timeout is reached "+ 66 "only the logs captured so far are saved to the archive.") 67 // include / exclude specs 68 cmd.PersistentFlags().StringSliceVar(&included, "include", bugReportDefaultInclude, 69 "Spec for which pod's proxy logs to include in the archive. See above for format and examples.") 70 cmd.PersistentFlags().StringSliceVar(&excluded, "exclude", bugReportDefaultExclude, 71 "Spec for which pod's proxy logs to exclude from the archive, after the include spec "+ 72 "is processed. See above for format and examples.") 73 74 // log time ranges 75 cmd.PersistentFlags().StringVar(&startTime, "start-time", "", 76 "Start time for the range of log entries to include in the archive. "+ 77 "Default is the infinite past. If set, --duration must be unset.") 78 cmd.PersistentFlags().StringVar(&endTime, "end-time", "", 79 "End time for the range of log entries to include in the archive. Default is now.") 80 cmd.PersistentFlags().DurationVar(&since, "duration", 0, 81 "How far to go back in time from end-time for log entries to include in the archive. "+ 82 "Default is infinity. If set, --start-time must be unset.") 83 84 // log error control 85 cmd.PersistentFlags().StringSliceVar(&args.CriticalErrors, "critical-errs", nil, 86 "List of comma separated glob patterns to match against log error strings. "+ 87 "If any pattern matches an error in the log, the logs is given the highest priority for archive inclusion.") 88 cmd.PersistentFlags().StringSliceVar(&args.IgnoredErrors, "ignore-errs", nil, 89 "List of comma separated glob patterns to match against log error strings. "+ 90 "Any error matching these patterns is ignored when calculating the log importance heuristic.") 91 92 // working dir to store temporary artifacts 93 cmd.PersistentFlags().StringVar(&tempDir, "dir", "", 94 "Set a specific directory for temporary artifact storage.") 95 96 cmd.PersistentFlags().StringVar(&outputDir, "output-dir", "", 97 "Set a specific directory for output archive file.") 98 99 // in-flight request limit 100 cmd.PersistentFlags().IntVar(&args.RequestConcurrency, "rq-concurrency", 0, 101 "Set the concurrency limit of requests to the Kubernetes API server, defaults to 32.") 102 } 103 104 func parseConfig() (*config2.BugReportConfig, error) { 105 fileConfig := &config2.BugReportConfig{} 106 if configFile != "" { 107 b, err := os.ReadFile(configFile) 108 if err != nil { 109 return nil, err 110 } 111 if err := yaml.Unmarshal(b, fileConfig); err != nil { 112 return nil, err 113 } 114 } 115 116 if err := parseTimes(gConfig, startTime, endTime, since); err != nil { 117 return nil, err 118 } 119 gConfig.CommandTimeout = config2.Duration(commandTimeout) 120 for _, s := range included { 121 ss := &config2.SelectionSpec{} 122 if err := ss.UnmarshalJSON([]byte(s)); err != nil { 123 return nil, err 124 } 125 gConfig.Include = append(gConfig.Include, ss) 126 } 127 // Exclude default system namespaces eg: bugReportDefaultExclude 128 dss := &config2.SelectionSpec{} 129 if err := dss.UnmarshalJSON([]byte(bugReportDefaultExclude[0])); err != nil { 130 return nil, err 131 } 132 gConfig.Exclude = append(gConfig.Exclude, dss) 133 // Exclude namespace provided by --exclude flag 134 for _, s := range excluded { 135 ess := &config2.SelectionSpec{} 136 if err := ess.UnmarshalJSON([]byte(s)); err != nil { 137 return nil, err 138 } 139 ess.Namespaces = slices.FilterInPlace(ess.Namespaces, func(ns string) bool { return !inject.IgnoredNamespaces.Contains(ns) }) 140 if len(ess.Namespaces) > 0 { 141 gConfig.Exclude = append(gConfig.Exclude, ess) 142 } 143 } 144 return overlayConfig(fileConfig, gConfig) 145 } 146 147 func parseTimes(config *config2.BugReportConfig, startTime, endTime string, duration time.Duration) error { 148 if startTime == "" && endTime == "" { 149 config.TimeFilterApplied = false 150 } else { 151 config.TimeFilterApplied = true 152 } 153 154 config.EndTime = time.Now() 155 config.Since = config2.Duration(duration) 156 if endTime != "" { 157 var err error 158 config.EndTime, err = time.Parse(time.RFC3339, endTime) 159 if err != nil { 160 return fmt.Errorf("bad format for end-time: %s, expect RFC3339 e.g. %s", endTime, time.RFC3339) 161 } 162 } 163 if config.Since != 0 { 164 if startTime != "" { 165 return fmt.Errorf("only one --start-time or --duration may be set") 166 } 167 config.StartTime = config.EndTime.Add(-1 * time.Duration(config.Since)) 168 } else { 169 var err error 170 if startTime == "" { 171 config.StartTime = time.Time{} 172 } else { 173 config.StartTime, err = time.Parse(time.RFC3339, startTime) 174 if err != nil { 175 return fmt.Errorf("bad format for start-time: %s, expect RFC3339 e.g. %s", startTime, time.RFC3339) 176 } 177 if config.StartTime.After(config.EndTime) { 178 return fmt.Errorf("bad format for start-time and end-time: start-time is after end-time") 179 } 180 } 181 } 182 return nil 183 } 184 185 func overlayConfig(base, overlay *config2.BugReportConfig) (*config2.BugReportConfig, error) { 186 bj, err := json.Marshal(base) 187 if err != nil { 188 return nil, err 189 } 190 oj, err := json.Marshal(overlay) 191 if err != nil { 192 return nil, err 193 } 194 195 mj, err := jsonpatch.MergePatch(bj, oj) 196 if err != nil { 197 return nil, fmt.Errorf("json merge error (%s) for base object: \n%s\n override object: \n%s", err, bj, oj) 198 } 199 200 out := &config2.BugReportConfig{} 201 err = json.Unmarshal(mj, out) 202 return out, err 203 }