github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/commands/report.go (about)

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"runtime"
    11  	"strings"
    12  	"text/template"
    13  
    14  	"github.com/spf13/cobra"
    15  
    16  	"github.com/buildpacks/pack/internal/build"
    17  	"github.com/buildpacks/pack/internal/builder"
    18  	"github.com/buildpacks/pack/pkg/logging"
    19  )
    20  
    21  func Report(logger logging.Logger, version, cfgPath string) *cobra.Command {
    22  	var explicit bool
    23  
    24  	cmd := &cobra.Command{
    25  		Use:     "report",
    26  		Args:    cobra.NoArgs,
    27  		Short:   "Display useful information for reporting an issue",
    28  		Example: "pack report",
    29  		RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
    30  			var buf bytes.Buffer
    31  			err := generateOutput(&buf, version, cfgPath, explicit)
    32  			if err != nil {
    33  				return err
    34  			}
    35  
    36  			logger.Info(buf.String())
    37  
    38  			return nil
    39  		}),
    40  	}
    41  
    42  	cmd.Flags().BoolVarP(&explicit, "explicit", "e", false, "Print config without redacting information")
    43  	AddHelpFlag(cmd, "report")
    44  	return cmd
    45  }
    46  
    47  func generateOutput(writer io.Writer, version, cfgPath string, explicit bool) error {
    48  	tpl := template.Must(template.New("").Parse(`Pack:
    49    Version:  {{ .Version }}
    50    OS/Arch:  {{ .OS }}/{{ .Arch }}
    51  
    52  Default Lifecycle Version:  {{ .DefaultLifecycleVersion }}
    53  
    54  Supported Platform APIs:  {{ .SupportedPlatformAPIs }}
    55  
    56  Config:
    57  {{ .Config -}}`))
    58  
    59  	configData := ""
    60  	if data, err := os.ReadFile(filepath.Clean(cfgPath)); err != nil {
    61  		configData = fmt.Sprintf("(no config file found at %s)", cfgPath)
    62  	} else {
    63  		var padded strings.Builder
    64  
    65  		for _, line := range strings.Split(string(data), "\n") {
    66  			if !explicit {
    67  				line = sanitize(line)
    68  			}
    69  			_, _ = fmt.Fprintf(&padded, "  %s\n", line)
    70  		}
    71  		configData = strings.TrimRight(padded.String(), " \n")
    72  	}
    73  
    74  	platformAPIs := strings.Join(build.SupportedPlatformAPIVersions.AsStrings(), ", ")
    75  
    76  	return tpl.Execute(writer, map[string]string{
    77  		"Version":                 version,
    78  		"OS":                      runtime.GOOS,
    79  		"Arch":                    runtime.GOARCH,
    80  		"DefaultLifecycleVersion": builder.DefaultLifecycleVersion,
    81  		"SupportedPlatformAPIs":   platformAPIs,
    82  		"Config":                  configData,
    83  	})
    84  }
    85  
    86  func sanitize(line string) string {
    87  	re := regexp.MustCompile(`"(.*?)"`)
    88  	redactedString := `"[REDACTED]"`
    89  	sensitiveFields := []string{
    90  		"default-builder-image",
    91  		"image",
    92  		"mirrors",
    93  		"name",
    94  		"url",
    95  	}
    96  	for _, field := range sensitiveFields {
    97  		if strings.HasPrefix(strings.TrimSpace(line), field) {
    98  			return re.ReplaceAllString(line, redactedString)
    99  		}
   100  	}
   101  
   102  	return line
   103  }