8000 feat: Export command - Export SBOM from storage by houdini91 · Pull Request #75 · bomctl/bomctl · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: Export command - Export SBOM from storage #75

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

Merged
merged 15 commits into from
Jun 26, 2024
123 changes: 123 additions & 0 deletions cmd/export.go
10000
Original file line numberDiff 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
0