blob: 9c010b08df424333ea58a11dc86ad2addc0da514 [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 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)
}