github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/cmd/syft/cli/commands/convert.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  
     8  	"github.com/spf13/cobra"
     9  
    10  	"github.com/anchore/clio"
    11  	"github.com/anchore/syft/cmd/syft/cli/options"
    12  	"github.com/anchore/syft/syft/format"
    13  	"github.com/lineaje-labs/syft/cmd/syft/internal/ui"
    14  	"github.com/lineaje-labs/syft/internal"
    15  	"github.com/lineaje-labs/syft/internal/log"
    16  )
    17  
    18  const (
    19  	convertExample = `  {{.appName}} {{.command}} img.syft.json -o spdx-json                      convert a syft SBOM to spdx-json, output goes to stdout
    20    {{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json    convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json""
    21    {{.appName}} {{.command}} - -o spdx-json                                  convert an SBOM from STDIN to spdx-json
    22  `
    23  )
    24  
    25  type ConvertOptions struct {
    26  	options.Config      `yaml:",inline" mapstructure:",squash"`
    27  	options.Output      `yaml:",inline" mapstructure:",squash"`
    28  	options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
    29  }
    30  
    31  //nolint:dupl
    32  func Convert(app clio.Application) *cobra.Command {
    33  	id := app.ID()
    34  
    35  	opts := &ConvertOptions{
    36  		UpdateCheck: options.DefaultUpdateCheck(),
    37  		Output:      options.DefaultOutput(),
    38  	}
    39  
    40  	return app.SetupCommand(&cobra.Command{
    41  		Use:   "convert [SOURCE-SBOM] -o [FORMAT]",
    42  		Short: "Convert between SBOM formats",
    43  		Long:  "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft#format-conversion-experimental",
    44  		Example: internal.Tprintf(convertExample, map[string]interface{}{
    45  			"appName": id.Name,
    46  			"command": "convert",
    47  		}),
    48  		Args:    validateConvertArgs,
    49  		PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
    50  		RunE: func(cmd *cobra.Command, args []string) error {
    51  			restoreStdout := ui.CaptureStdoutToTraceLog()
    52  			defer restoreStdout()
    53  
    54  			return RunConvert(opts, args[0])
    55  		},
    56  	}, opts)
    57  }
    58  
    59  func validateConvertArgs(cmd *cobra.Command, args []string) error {
    60  	return validateArgs(cmd, args, "an SBOM argument is required")
    61  }
    62  
    63  func RunConvert(opts *ConvertOptions, userInput string) error {
    64  	log.Warn("convert is an experimental feature, run `syft convert -h` for help")
    65  
    66  	writer, err := opts.SBOMWriter()
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	var reader io.ReadSeekCloser
    72  
    73  	if userInput == "-" {
    74  		// though os.Stdin is an os.File, it does not support seeking
    75  		// you will get errors such as "seek /dev/stdin: illegal seek".
    76  		// We need to buffer what we read.
    77  		reader = internal.NewBufferedSeeker(os.Stdin)
    78  	} else {
    79  		f, err := os.Open(userInput)
    80  		if err != nil {
    81  			return fmt.Errorf("failed to open SBOM file: %w", err)
    82  		}
    83  		defer func() {
    84  			_ = f.Close()
    85  		}()
    86  		reader = f
    87  	}
    88  
    89  	s, _, _, err := format.Decode(reader)
    90  	if err != nil {
    91  		return fmt.Errorf("failed to decode SBOM: %w", err)
    92  	}
    93  
    94  	if s == nil {
    95  		return fmt.Errorf("no SBOM produced")
    96  	}
    97  
    98  	if err := writer.Write(*s); err != nil {
    99  		return fmt.Errorf("failed to write SBOM: %w", err)
   100  	}
   101  
   102  	return nil
   103  }