| // 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 mesh |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "sort" |
| "strings" |
| ) |
| |
| import ( |
| "github.com/spf13/cobra" |
| "istio.io/pkg/log" |
| "sigs.k8s.io/yaml" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/manifest" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/tpath" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/util" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/util/clog" |
| ) |
| |
| type profileDumpArgs struct { |
| // inFilenames is an array of paths to the input IstioOperator CR files. |
| inFilenames []string |
| // configPath sets the root node for the subtree to display the config for. |
| configPath string |
| // outputFormat controls the format of profile dumps |
| outputFormat string |
| // manifestsPath is a path to a charts and profiles directory in the local filesystem, or URL with a release tgz. |
| manifestsPath string |
| } |
| |
| const ( |
| jsonOutput = "json" |
| yamlOutput = "yaml" |
| flagsOutput = "flags" |
| ) |
| |
| const ( |
| istioOperatorTreeString = ` |
| apiVersion: install.istio.io/v1alpha1 |
| kind: IstioOperator |
| ` |
| ) |
| |
| func addProfileDumpFlags(cmd *cobra.Command, args *profileDumpArgs) { |
| cmd.PersistentFlags().StringSliceVarP(&args.inFilenames, "filename", "f", nil, filenameFlagHelpStr) |
| cmd.PersistentFlags().StringVarP(&args.configPath, "config-path", "p", "", |
| "The path the root of the configuration subtree to dump e.g. components.pilot. By default, dump whole tree") |
| cmd.PersistentFlags().StringVarP(&args.outputFormat, "output", "o", yamlOutput, |
| "Output format: one of json|yaml|flags") |
| cmd.PersistentFlags().StringVarP(&args.manifestsPath, "charts", "", "", ChartsDeprecatedStr) |
| cmd.PersistentFlags().StringVarP(&args.manifestsPath, "manifests", "d", "", ManifestsFlagHelpStr) |
| } |
| |
| func profileDumpCmd(rootArgs *RootArgs, pdArgs *profileDumpArgs, logOpts *log.Options) *cobra.Command { |
| return &cobra.Command{ |
| Use: "dump [<profile>]", |
| Short: "Dumps an Istio configuration profile", |
| Long: "The dump subcommand dumps the values in an Istio configuration profile.", |
| Args: func(cmd *cobra.Command, args []string) error { |
| if len(args) > 1 { |
| return fmt.Errorf("too many positional arguments") |
| } |
| return nil |
| }, |
| RunE: func(cmd *cobra.Command, args []string) error { |
| l := clog.NewConsoleLogger(cmd.OutOrStdout(), cmd.ErrOrStderr(), installerScope) |
| return profileDump(args, rootArgs, pdArgs, l, logOpts) |
| }, |
| } |
| } |
| |
| func prependHeader(yml string) (string, error) { |
| out, err := tpath.AddSpecRoot(yml) |
| if err != nil { |
| return "", err |
| } |
| out2, err := util.OverlayYAML(istioOperatorTreeString, out) |
| if err != nil { |
| return "", err |
| } |
| return out2, nil |
| } |
| |
| // Convert the generated YAML to pretty JSON. |
| func yamlToPrettyJSON(yml string) (string, error) { |
| // YAML objects are not completely compatible with JSON |
| // objects. Let yaml.YAMLToJSON handle the edge cases and |
| // we'll re-encode the result to pretty JSON. |
| uglyJSON, err := yaml.YAMLToJSON([]byte(yml)) |
| if err != nil { |
| return "", err |
| } |
| var decoded interface{} |
| if uglyJSON[0] == '[' { |
| decoded = make([]interface{}, 0) |
| } else { |
| decoded = map[string]interface{}{} |
| } |
| if err := json.Unmarshal(uglyJSON, &decoded); err != nil { |
| return "", err |
| } |
| prettyJSON, err := json.MarshalIndent(decoded, "", " ") |
| if err != nil { |
| return "", err |
| } |
| return string(prettyJSON), nil |
| } |
| |
| func profileDump(args []string, rootArgs *RootArgs, pdArgs *profileDumpArgs, l clog.Logger, logOpts *log.Options) error { |
| initLogsOrExit(rootArgs) |
| |
| if len(args) == 1 && pdArgs.inFilenames != nil { |
| return fmt.Errorf("cannot specify both profile name and filename flag") |
| } |
| |
| if err := validateProfileOutputFormatFlag(pdArgs.outputFormat); err != nil { |
| return err |
| } |
| |
| setFlags := applyFlagAliases(make([]string, 0), pdArgs.manifestsPath, "") |
| if len(args) == 1 { |
| setFlags = append(setFlags, "profile="+args[0]) |
| } |
| |
| if err := configLogs(logOpts); err != nil { |
| return fmt.Errorf("could not configure logs: %s", err) |
| } |
| |
| y, _, err := manifest.GenerateConfig(pdArgs.inFilenames, setFlags, true, nil, l) |
| if err != nil { |
| return err |
| } |
| y, err = tpath.GetConfigSubtree(y, "spec") |
| if err != nil { |
| return err |
| } |
| |
| if pdArgs.configPath == "" { |
| if y, err = prependHeader(y); err != nil { |
| return err |
| } |
| } else { |
| if y, err = tpath.GetConfigSubtree(y, pdArgs.configPath); err != nil { |
| return err |
| } |
| } |
| |
| var output string |
| if output, err = yamlToFormat(y, pdArgs.outputFormat); err != nil { |
| return err |
| } |
| l.Print(output) |
| return nil |
| } |
| |
| // validateOutputFormatFlag validates if the output format is valid. |
| func validateProfileOutputFormatFlag(outputFormat string) error { |
| switch outputFormat { |
| case jsonOutput, yamlOutput, flagsOutput: |
| default: |
| return fmt.Errorf("unknown output format: %s", outputFormat) |
| } |
| return nil |
| } |
| |
| // yamlToFormat converts the generated yaml config to the expected format |
| func yamlToFormat(yaml, outputFormat string) (string, error) { |
| var output string |
| switch outputFormat { |
| case jsonOutput: |
| j, err := yamlToPrettyJSON(yaml) |
| if err != nil { |
| return "", err |
| } |
| output = fmt.Sprintf("%s\n", j) |
| case yamlOutput: |
| output = fmt.Sprintf("%s\n", yaml) |
| case flagsOutput: |
| f, err := yamlToFlags(yaml) |
| if err != nil { |
| return "", err |
| } |
| output = fmt.Sprintf("%s\n", strings.Join(f, "\n")) |
| } |
| return output, nil |
| } |
| |
| // Convert the generated YAML to --set flags |
| func yamlToFlags(yml string) ([]string, error) { |
| // YAML objects are not completely compatible with JSON |
| // objects. Let yaml.YAMLToJSON handle the edge cases and |
| // we'll re-encode the result to pretty JSON. |
| uglyJSON, err := yaml.YAMLToJSON([]byte(yml)) |
| if err != nil { |
| return []string{}, err |
| } |
| var decoded interface{} |
| if uglyJSON[0] == '[' { |
| decoded = make([]interface{}, 0) |
| } else { |
| decoded = map[string]interface{}{} |
| } |
| if err := json.Unmarshal(uglyJSON, &decoded); err != nil { |
| return []string{}, err |
| } |
| if d, ok := decoded.(map[string]interface{}); ok { |
| if v, ok := d["spec"]; ok { |
| // Fall back to showing the entire spec. |
| // (When --config-path is used there will be no spec to remove) |
| decoded = v |
| } |
| } |
| setflags, err := walk("", "", decoded) |
| if err != nil { |
| return []string{}, err |
| } |
| sort.Strings(setflags) |
| return setflags, nil |
| } |
| |
| func walk(path, separator string, obj interface{}) ([]string, error) { |
| switch v := obj.(type) { |
| case map[string]interface{}: |
| accum := make([]string, 0) |
| for key, vv := range v { |
| childwalk, err := walk(fmt.Sprintf("%s%s%s", path, separator, pathComponent(key)), ".", vv) |
| if err != nil { |
| return accum, err |
| } |
| accum = append(accum, childwalk...) |
| } |
| return accum, nil |
| case []interface{}: |
| accum := make([]string, 0) |
| for idx, vv := range v { |
| indexwalk, err := walk(fmt.Sprintf("%s[%d]", path, idx), ".", vv) |
| if err != nil { |
| return accum, err |
| } |
| accum = append(accum, indexwalk...) |
| } |
| return accum, nil |
| case string: |
| return []string{fmt.Sprintf("%s=%q", path, v)}, nil |
| default: |
| return []string{fmt.Sprintf("%s=%v", path, v)}, nil |
| } |
| } |
| |
| func pathComponent(component string) string { |
| if !strings.Contains(component, util.PathSeparator) { |
| return component |
| } |
| return strings.ReplaceAll(component, util.PathSeparator, util.EscapedPathSeparator) |
| } |