github.com/wolfi-dev/wolfictl@v0.16.11/pkg/cli/advisory_export.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/google/osv-scanner/pkg/models"
    10  	"github.com/spf13/cobra"
    11  
    12  	"github.com/chainguard-dev/clog"
    13  
    14  	"github.com/wolfi-dev/wolfictl/pkg/advisory"
    15  	"github.com/wolfi-dev/wolfictl/pkg/configs"
    16  	v2 "github.com/wolfi-dev/wolfictl/pkg/configs/advisory/v2"
    17  	rwos "github.com/wolfi-dev/wolfictl/pkg/configs/rwfs/os"
    18  	"github.com/wolfi-dev/wolfictl/pkg/distro"
    19  )
    20  
    21  func cmdAdvisoryExport() *cobra.Command {
    22  	p := &exportParams{}
    23  	cmd := &cobra.Command{
    24  		Use:           "export",
    25  		Short:         "Export advisory data (experimental)",
    26  		SilenceErrors: true,
    27  		Args:          cobra.NoArgs,
    28  		Hidden:        true,
    29  
    30  		PreRunE: func(cmd *cobra.Command, _ []string) error {
    31  			ctx := cmd.Context()
    32  
    33  			if p.format == OutputOSV {
    34  				if _, err := os.Stat(p.outputLocation); os.IsNotExist(err) {
    35  					clog.FromContext(ctx).Errorf("directory %s does not exist, please create that first", p.outputLocation)
    36  					return err
    37  				}
    38  			}
    39  
    40  			return nil
    41  		},
    42  
    43  		RunE: func(cmd *cobra.Command, _ []string) error {
    44  			if len(p.advisoriesRepoDirs) == 0 {
    45  				if p.doNotDetectDistro {
    46  					return fmt.Errorf("no advisories repo dir specified")
    47  				}
    48  
    49  				d, err := distro.Detect()
    50  				if err != nil {
    51  					return fmt.Errorf("no advisories repo dir specified, and distro auto-detection failed: %w", err)
    52  				}
    53  
    54  				p.advisoriesRepoDirs = append(p.advisoriesRepoDirs, d.Local.AdvisoriesRepo.Dir)
    55  				_, _ = fmt.Fprint(os.Stderr, renderDetectedDistro(d))
    56  			}
    57  
    58  			indices := make([]*configs.Index[v2.Document], 0, len(p.advisoriesRepoDirs))
    59  			for _, dir := range p.advisoriesRepoDirs {
    60  				advisoryFsys := rwos.DirFS(dir)
    61  				index, err := v2.NewIndex(cmd.Context(), advisoryFsys)
    62  				if err != nil {
    63  					return fmt.Errorf("unable to index advisory configs for directory %q: %w", dir, err)
    64  				}
    65  
    66  				indices = append(indices, index)
    67  			}
    68  
    69  			opts := advisory.ExportOptions{
    70  				AdvisoryDocIndices: indices,
    71  				Ecosystem:          models.Ecosystem(p.ecosystem),
    72  			}
    73  
    74  			var export io.Reader
    75  			var err error
    76  			switch p.format {
    77  			case OutputYAML:
    78  				export, err = advisory.ExportYAML(opts)
    79  			case OutputCSV:
    80  				export, err = advisory.ExportCSV(opts)
    81  			case OutputOSV:
    82  				err = advisory.ExportOSV(opts, p.outputLocation)
    83  			default:
    84  				return fmt.Errorf("unrecognized format: %q. Valid formats are: [%s]", p.format, strings.Join([]string{OutputYAML, OutputCSV, OutputOSV}, ", "))
    85  			}
    86  			if err != nil {
    87  				return fmt.Errorf("unable to export advisory data: %w", err)
    88  			}
    89  
    90  			if p.format != OutputOSV {
    91  				var outputFile *os.File
    92  				if p.outputLocation == "" {
    93  					outputFile = os.Stdout
    94  				} else {
    95  					outputFile, err = os.Create(p.outputLocation)
    96  					if err != nil {
    97  						return fmt.Errorf("unable to create output file: %w", err)
    98  					}
    99  					defer outputFile.Close()
   100  				}
   101  
   102  				_, err = io.Copy(outputFile, export)
   103  				if err != nil {
   104  					return fmt.Errorf("unable to export data to specified location: %w", err)
   105  				}
   106  			}
   107  
   108  			return nil
   109  		},
   110  	}
   111  
   112  	p.addFlagsTo(cmd)
   113  	return cmd
   114  }
   115  
   116  type exportParams struct {
   117  	doNotDetectDistro  bool
   118  	advisoriesRepoDirs []string
   119  	outputLocation     string
   120  	// format controls how commands will produce their output.
   121  	format    string
   122  	ecosystem string
   123  }
   124  
   125  const (
   126  	// OutputYAML YAML output.
   127  	OutputYAML = "yaml"
   128  	// OutputCSV CSV output.
   129  	OutputCSV = "csv"
   130  	// OutputOSV OSV output.
   131  	OutputOSV = "osv"
   132  )
   133  
   134  func (p *exportParams) addFlagsTo(cmd *cobra.Command) {
   135  	addNoDistroDetectionFlag(&p.doNotDetectDistro, cmd)
   136  
   137  	cmd.Flags().StringSliceVarP(&p.advisoriesRepoDirs, "advisories-repo-dir", "a", nil, "directory containing an advisories repository")
   138  	cmd.Flags().StringVarP(&p.outputLocation, "output", "o", "", "output location (default: stdout). In case using OSV format this will be the output directory.")
   139  	cmd.Flags().StringVarP(&p.format, "format", "f", OutputCSV, fmt.Sprintf("Output format. One of: [%s]", strings.Join([]string{OutputYAML, OutputCSV, OutputOSV}, ", ")))
   140  	cmd.Flags().StringVarP(&p.ecosystem, "ecosystem", "e", "Wolfi", fmt.Sprintf("Ecosystem format. One of: [%s]", strings.Join([]string{"Wolfi", "Chainguard"}, ", ")))
   141  }