istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/util/formatting/formatter.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 formatting
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"strings"
    23  
    24  	"github.com/mattn/go-isatty"
    25  	"sigs.k8s.io/yaml"
    26  
    27  	"istio.io/istio/pkg/config/analysis/diag"
    28  	"istio.io/istio/pkg/env"
    29  )
    30  
    31  // Formatting options for Messages
    32  const (
    33  	LogFormat  = "log"
    34  	JSONFormat = "json"
    35  	YAMLFormat = "yaml"
    36  )
    37  
    38  var (
    39  	MsgOutputFormatKeys = []string{LogFormat, JSONFormat, YAMLFormat}
    40  	MsgOutputFormats    = make(map[string]bool)
    41  	termEnvVar          = env.Register("TERM", "", "Specifies terminal type.  Use 'dumb' to suppress color output")
    42  )
    43  
    44  func init() {
    45  	for _, key := range MsgOutputFormatKeys {
    46  		MsgOutputFormats[key] = true
    47  	}
    48  }
    49  
    50  // Print output messages in the specified format with color options
    51  func Print(ms diag.Messages, format string, colorize bool) (string, error) {
    52  	switch format {
    53  	case LogFormat:
    54  		return printLog(ms, colorize), nil
    55  	case JSONFormat:
    56  		return printJSON(ms)
    57  	case YAMLFormat:
    58  		return printYAML(ms)
    59  	default:
    60  		return "", fmt.Errorf("invalid format, expected one of %v but got %q", MsgOutputFormatKeys, format)
    61  	}
    62  }
    63  
    64  func printLog(ms diag.Messages, colorize bool) string {
    65  	logOutput := make([]string, 0, len(ms))
    66  	for _, m := range ms {
    67  		logOutput = append(logOutput, render(m, colorize))
    68  	}
    69  	return strings.Join(logOutput, "\n")
    70  }
    71  
    72  func printJSON(ms diag.Messages) (string, error) {
    73  	jsonOutput, err := json.MarshalIndent(ms, "", "\t")
    74  	return string(jsonOutput), err
    75  }
    76  
    77  func printYAML(ms diag.Messages) (string, error) {
    78  	yamlOutput, err := yaml.Marshal(ms)
    79  	return string(yamlOutput), err
    80  }
    81  
    82  // Formatting options for Message
    83  var (
    84  	colorPrefixes = map[diag.Level]string{
    85  		diag.Info:    "",           // no special color for info messages
    86  		diag.Warning: "\033[33m",   // yellow
    87  		diag.Error:   "\033[1;31m", // bold red
    88  	}
    89  )
    90  
    91  // render turns a Message instance into a string with an option of colored bash output
    92  func render(m diag.Message, colorize bool) string {
    93  	return fmt.Sprintf("%s%v%s [%v]%s %s",
    94  		colorPrefix(m, colorize), m.Type.Level(), colorSuffix(colorize),
    95  		m.Type.Code(), m.Origin(), fmt.Sprintf(m.Type.Template(), m.Parameters...),
    96  	)
    97  }
    98  
    99  func colorPrefix(m diag.Message, colorize bool) string {
   100  	if !colorize {
   101  		return ""
   102  	}
   103  
   104  	prefix, ok := colorPrefixes[m.Type.Level()]
   105  	if !ok {
   106  		return ""
   107  	}
   108  	return prefix
   109  }
   110  
   111  func colorSuffix(colorize bool) string {
   112  	if !colorize {
   113  		return ""
   114  	}
   115  	return "\033[0m"
   116  }
   117  
   118  func IstioctlColorDefault(writer io.Writer) bool {
   119  	if strings.EqualFold(termEnvVar.Get(), "dumb") {
   120  		return false
   121  	}
   122  
   123  	file, ok := writer.(*os.File)
   124  	if ok {
   125  		if !isatty.IsTerminal(file.Fd()) {
   126  			return false
   127  		}
   128  	}
   129  
   130  	return true
   131  }