blob: 0859444fe8c5e76052bc2f56aeb640a0e02a01c0 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bugreport
import (
"encoding/json"
"fmt"
"os"
"time"
)
import (
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/kube/inject"
config2 "github.com/apache/dubbo-go-pixiu/tools/bug-report/pkg/config"
)
var (
startTime, endTime, configFile, tempDir string
included, excluded []string
commandTimeout, since time.Duration
gConfig = &config2.BugReportConfig{}
)
func addFlags(cmd *cobra.Command, args *config2.BugReportConfig) {
// k8s client config
cmd.PersistentFlags().StringVarP(&args.KubeConfigPath, "kubeconfig", "c", "",
"Path to kube config.")
cmd.PersistentFlags().StringVar(&args.Context, "context", "",
"Name of the kubeconfig Context to use.")
// input config
cmd.PersistentFlags().StringVarP(&configFile, "filename", "f", "",
"Path to a file containing configuration in YAML format. The file contents are applied over the default "+
"values and flag settings, with lists being replaced per JSON merge semantics.")
// dry run
cmd.PersistentFlags().BoolVarP(&args.DryRun, "dry-run", "", false,
"Only log commands that would be run, don't fetch or write.")
// full secrets
cmd.PersistentFlags().BoolVarP(&args.FullSecrets, "full-secrets", "", false,
"If set, secret contents are included in output.")
// istio namespaces
cmd.PersistentFlags().StringVar(&args.IstioNamespace, "istio-namespace", bugReportDefaultIstioNamespace,
"Namespace where Istio control plane is installed.")
// timeouts and max sizes
cmd.PersistentFlags().DurationVar(&commandTimeout, "timeout", bugReportDefaultTimeout,
"Maximum amount of time to spend fetching logs. When timeout is reached "+
"only the logs captured so far are saved to the archive.")
// include / exclude specs
cmd.PersistentFlags().StringSliceVar(&included, "include", bugReportDefaultInclude,
"Spec for which pod's proxy logs to include in the archive. See above for format and examples.")
cmd.PersistentFlags().StringSliceVar(&excluded, "exclude", bugReportDefaultExclude,
"Spec for which pod's proxy logs to exclude from the archive, after the include spec "+
"is processed. See above for format and examples.")
// log time ranges
cmd.PersistentFlags().StringVar(&startTime, "start-time", "",
"Start time for the range of log entries to include in the archive. "+
"Default is the infinite past. If set, --duration must be unset.")
cmd.PersistentFlags().StringVar(&endTime, "end-time", "",
"End time for the range of log entries to include in the archive. Default is now.")
cmd.PersistentFlags().DurationVar(&since, "duration", 0,
"How far to go back in time from end-time for log entries to include in the archive. "+
"Default is infinity. If set, --start-time must be unset.")
// log error control
cmd.PersistentFlags().StringSliceVar(&args.CriticalErrors, "critical-errs", nil,
"List of comma separated glob patterns to match against log error strings. "+
"If any pattern matches an error in the log, the logs is given the highest priority for archive inclusion.")
cmd.PersistentFlags().StringSliceVar(&args.IgnoredErrors, "ignore-errs", nil,
"List of comma separated glob patterns to match against log error strings. "+
"Any error matching these patterns is ignored when calculating the log importance heuristic.")
// output/working dir
cmd.PersistentFlags().StringVar(&tempDir, "dir", "",
"Set a specific directory for temporary artifact storage.")
}
func parseConfig() (*config2.BugReportConfig, error) {
fileConfig := &config2.BugReportConfig{}
if configFile != "" {
b, err := os.ReadFile(configFile)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(b, fileConfig); err != nil {
return nil, err
}
}
if err := parseTimes(gConfig, startTime, endTime, since); err != nil {
return nil, err
}
gConfig.CommandTimeout = config2.Duration(commandTimeout)
for _, s := range included {
ss := &config2.SelectionSpec{}
if err := ss.UnmarshalJSON([]byte(s)); err != nil {
return nil, err
}
gConfig.Include = append(gConfig.Include, ss)
}
// Exclude default system namespaces eg: bugReportDefaultExclude
dss := &config2.SelectionSpec{}
if err := dss.UnmarshalJSON([]byte(bugReportDefaultExclude[0])); err != nil {
return nil, err
}
gConfig.Exclude = append(gConfig.Exclude, dss)
// Exclude namespace provided by --exclude flag
for _, s := range excluded {
ess := &config2.SelectionSpec{}
if err := ess.UnmarshalJSON([]byte(s)); err != nil {
return nil, err
}
ess.Namespaces = filterSystemNamespacesOut(ess.Namespaces)
if len(ess.Namespaces) > 0 {
gConfig.Exclude = append(gConfig.Exclude, ess)
}
}
return overlayConfig(fileConfig, gConfig)
}
func parseTimes(config *config2.BugReportConfig, startTime, endTime string, duration time.Duration) error {
config.EndTime = time.Now()
config.Since = config2.Duration(duration)
if endTime != "" {
var err error
config.EndTime, err = time.Parse(time.RFC3339, endTime)
if err != nil {
return fmt.Errorf("bad format for end-time: %s, expect RFC3339 e.g. %s", endTime, time.RFC3339)
}
}
if config.Since != 0 {
if startTime != "" {
return fmt.Errorf("only one --start-time or --duration may be set")
}
config.StartTime = config.EndTime.Add(-1 * time.Duration(config.Since))
} else {
var err error
if startTime == "" {
config.StartTime = time.Time{}
} else {
config.StartTime, err = time.Parse(time.RFC3339, startTime)
if err != nil {
return fmt.Errorf("bad format for start-time: %s, expect RFC3339 e.g. %s", startTime, time.RFC3339)
}
if config.StartTime.After(config.EndTime) {
return fmt.Errorf("bad format for start-time and end-time: start-time is after end-time")
}
}
}
return nil
}
func overlayConfig(base, overlay *config2.BugReportConfig) (*config2.BugReportConfig, error) {
bj, err := json.Marshal(base)
if err != nil {
return nil, err
}
oj, err := json.Marshal(overlay)
if err != nil {
return nil, err
}
mj, err := jsonpatch.MergePatch(bj, oj)
if err != nil {
return nil, fmt.Errorf("json merge error (%s) for base object: \n%s\n override object: \n%s", err, bj, oj)
}
out := &config2.BugReportConfig{}
err = json.Unmarshal(mj, out)
return out, err
}
func filterSystemNamespacesOut(namespaces []string) []string {
filteredNss := make([]string, 0)
for _, ns := range namespaces {
if inject.IgnoredNamespaces.Contains(ns) {
continue
}
filteredNss = append(filteredNss, ns)
}
return filteredNss
}