Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion/save command #2

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
123 changes: 123 additions & 0 deletions cmd/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// ------------------------------------------------------------------------
// SPDX-FileCopyrightText: Copyright © 2024 bomctl authors
// SPDX-FileName: cmd/export.go
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: Apache-2.0
// ------------------------------------------------------------------------
// 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 cmd

import (
"os"
"path/filepath"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/bomctl/bomctl/internal/pkg/db"
"github.com/bomctl/bomctl/internal/pkg/export"
"github.com/bomctl/bomctl/internal/pkg/utils"
"github.com/bomctl/bomctl/internal/pkg/utils/format"
)

func exportCmd() *cobra.Command {
documentIDs := []string{}
opts := &export.ExportOptions{
Logger: utils.NewLogger("export"),
}

outputFile := OutputFileValue("")
formatString := FormatStringValue(format.DefaultFormatString())
formatEncoding := FormatEncodingValue(format.DefaultEncoding())

exportCmd := &cobra.Command{
Use: "export [flags] SBOM_URL...",
Args: cobra.MinimumNArgs(1),
Short: "Export SBOM file(s) from Storage",
Long: "Export SBOM file(s) from Storage",
PreRun: func(_ *cobra.Command, args []string) {
documentIDs = append(documentIDs, args...)
},
Run: func(cmd *cobra.Command, _ []string) {
cfgFile, err := cmd.Flags().GetString("config")
cobra.CheckErr(err)

initOpts(opts, cfgFile, string(formatString), string(formatEncoding))
backend := initBackend(opts)

if string(outputFile) != "" {
if len(documentIDs) > 1 {
opts.Logger.Fatal("The --output-file option cannot be used when more than one SBOM is provided.")
}

out, err := os.Create(string(outputFile))
if err != nil {
opts.Logger.Fatal("error creating output file", "outputFile", outputFile)
}

opts.OutputFile = out

defer opts.OutputFile.Close()
}
Export(documentIDs, opts, backend)
},
}

exportCmd.Flags().VarP(
&outputFile,
"output-file",
"o",
"Path to output file",
)
exportCmd.Flags().VarP(
&formatString,
"format",
"f",
format.FormatStringOptions)
exportCmd.Flags().VarP(
&formatEncoding,
"encoding",
"e",
"the output encoding [spdx: [text, json] cyclonedx: [json]")

return exportCmd
}

func Export(documentIDs []string, opts *export.ExportOptions, backend *db.Backend) {
for _, id := range documentIDs {
if err := export.Export(id, opts, backend); err != nil {
opts.Logger.Fatal(err)
}
}
}

func initOpts(opts *export.ExportOptions, cfgFile, formatString, formatEncoding string) {
opts.CacheDir = viper.GetString("cache_dir")
opts.ConfigFile = cfgFile
opts.FormatString = formatString
opts.Encoding = formatEncoding
}

func initBackend(opts *export.ExportOptions) *db.Backend {
backend := db.NewBackend(func(b *db.Backend) {
b.Options.DatabaseFile = filepath.Join(opts.CacheDir, db.DatabaseFile)
b.Logger = utils.NewLogger("export")
})

if err := backend.InitClient(); err != nil {
backend.Logger.Fatalf("failed to initialize backend client: %v", err)
}

return backend
}
31 changes: 26 additions & 5 deletions cmd/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ type (
DirectoryValue string
ExistingFileValue string
OutputFileValue string
FormatStringValue string
FormatEncodingValue string
URLValue string
DirectorySliceValue []string
FileSliceValue []string
URLSliceValue []string
SBOMIDSliceValue []string
)

var (
Expand Down Expand Up @@ -63,8 +66,11 @@ func (dsv *DirectorySliceValue) String() string { return fmt.Sprintf("%v", *dsv)
func (efv *ExistingFileValue) String() string { return fmt.Sprintf("%v", *efv) }
func (fsv *FileSliceValue) String() string { return fmt.Sprintf("%v", *fsv) }
func (ofv *OutputFileValue) String() string { return fmt.Sprintf("%v", *ofv) }
func (uv *URLValue) String() string { return fmt.Sprintf("%v", *uv) }
func (usv *URLSliceValue) String() string { return fmt.Sprintf("%v", *usv) }
func (fstv *FormatStringValue) String() string { return fmt.Sprintf("%v", *fstv) }
func (fev *FormatEncodingValue) String() string { return fmt.Sprintf("%v", *fev) }

func (uv *URLValue) String() string { return fmt.Sprintf("%v", *uv) }
func (usv *URLSliceValue) String() string { return fmt.Sprintf("%v", *usv) }

func (dv *DirectoryValue) Set(value string) error {
checkDirectory(value)
Expand Down Expand Up @@ -100,6 +106,18 @@ func (ofv *OutputFileValue) Set(value string) error {
return nil
}

func (fstv *FormatStringValue) Set(value string) error {
*fstv = FormatStringValue(value)

return nil
}

func (fev *FormatEncodingValue) Set(value string) error {
*fev = FormatEncodingValue(value)

return nil
}

func (uv *URLValue) Set(value string) error {
*uv = URLValue(value)

Expand All @@ -113,15 +131,18 @@ func (usv *URLSliceValue) Set(value string) error {
}

const (
valueTypeDir string = "DIRECTORY"
valueTypeFile string = "FILE"
valueTypeURL string = "URL"
valueTypeDir string = "DIRECTORY"
valueTypeFile string = "FILE"
valueTypeURL string = "URL"
valueTypeString string = "STRING"
)

func (dv *DirectoryValue) Type() string { return valueTypeDir }
func (dsv *DirectorySliceValue) Type() string { return valueTypeDir }
func (efv *ExistingFileValue) Type() string { return valueTypeFile }
func (fsv *FileSliceValue) Type() string { return valueTypeFile }
func (ofv *OutputFileValue) Type() string { return valueTypeFile }
func (fstv *FormatStringValue) Type() string { return valueTypeString }
func (fev *FormatEncodingValue) Type() string { return valueTypeString }
func (uv *URLValue) Type() string { return valueTypeURL }
func (usv *URLSliceValue) Type() string { return valueTypeURL }
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func rootCmd() *cobra.Command {
cobra.CheckErr(viper.BindPFlag("cache_dir", rootCmd.PersistentFlags().Lookup("cache-dir")))

rootCmd.AddCommand(fetchCmd())
rootCmd.AddCommand(exportCmd())
rootCmd.AddCommand(listCmd())
rootCmd.AddCommand(versionCmd())

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/charmbracelet/lipgloss v0.9.1
github.com/charmbracelet/log v0.3.1
github.com/go-git/go-git/v5 v5.11.0
github.com/google/go-cmp v0.6.0
github.com/jdx/go-netrc v1.0.0
github.com/opencontainers/image-spec v1.1.0
github.com/protobom/protobom v0.4.2
Expand All @@ -29,6 +30,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
Expand All @@ -40,7 +42,6 @@ require (
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.13.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBS
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
Expand Down
76 changes: 76 additions & 0 deletions internal/pkg/export/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// ------------------------------------------------------------------------
// SPDX-FileCopyrightText: Copyright © 2024 bomctl authors
// SPDX-FileName: internal/pkg/export/export.go
// SPDX-FileType: SOURCE
// SPDX-License-Identifier: Apache-2.0
// ------------------------------------------------------------------------
// 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 export

import (
"fmt"
"os"

"github.com/charmbracelet/log"
"github.com/protobom/protobom/pkg/writer"

"github.com/bomctl/bomctl/internal/pkg/db"
"github.com/bomctl/bomctl/internal/pkg/utils"
"github.com/bomctl/bomctl/internal/pkg/utils/format"
)

type (
ExportOptions struct {
Logger *log.Logger
OutputFile *os.File
FormatString string
Encoding string
CacheDir string
ConfigFile string
}
)

func Export(sbomID string, opts *ExportOptions, backend *db.Backend) error {
logger := utils.NewLogger("export")

logger.Info(fmt.Sprintf("Exporting %s SBOM ID", sbomID))

parsedFormat, err := format.Parse(opts.FormatString, opts.Encoding)
if err != nil {
return fmt.Errorf("%w", err)
}

wr := writer.New(
writer.WithFormat(parsedFormat),
)

document, err := backend.GetDocumentByID(sbomID)
if err != nil {
return fmt.Errorf("%w", err)
}

if opts.OutputFile != nil {
// Write the SBOM document bytes to file.
if err := wr.WriteFile(document, opts.OutputFile.Name()); err != nil {
return fmt.Errorf("%w", err)
}
} else {
// Write the SBOM document bytes to stdout.
if err := wr.WriteStream(document, os.Stdout); err != nil {
return fmt.Errorf("%w", err)
}
}

return nil
}
Loading