-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8091bbc
commit 25ccb63
Showing
8 changed files
with
400 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package sbom | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/debricked/cli/internal/report" | ||
"github.com/debricked/cli/internal/report/sbom" | ||
"github.com/fatih/color" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
var commitId string | ||
var repositoryId string | ||
|
||
const CommitFlag = "commit" | ||
const RepositorylFlag = "repository" | ||
const TokenFlag = "token" | ||
|
||
func NewSBOMCmd(reporter report.IReporter) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "sbom", | ||
Short: "Generate SBOM report", | ||
Long: `Generate SBOM report for chosen commit and repository. | ||
This is an enterprise feature. Please visit https://debricked.com/pricing/ for more info.`, | ||
PreRun: func(cmd *cobra.Command, _ []string) { | ||
_ = viper.BindPFlags(cmd.Flags()) | ||
}, | ||
RunE: RunE(reporter), | ||
} | ||
|
||
cmd.Flags().StringVarP(&commitId, CommitFlag, "c", "", "The commit that you want an SBOM report for") | ||
_ = cmd.MarkFlagRequired(CommitFlag) | ||
viper.MustBindEnv(CommitFlag) | ||
cmd.Flags().StringVarP(&repositoryId, RepositorylFlag, "r", "", "The repository that you want an SBOM report for") | ||
_ = cmd.MarkFlagRequired(RepositorylFlag) | ||
viper.MustBindEnv(RepositorylFlag) | ||
|
||
return cmd | ||
} | ||
|
||
func RunE(r report.IReporter) func(_ *cobra.Command, args []string) error { | ||
return func(_ *cobra.Command, _ []string) error { | ||
orderArgs := sbom.OrderArgs{ | ||
RepositoryID: viper.GetString(RepositorylFlag), | ||
CommitID: viper.GetString(CommitFlag), | ||
} | ||
|
||
if err := r.Order(orderArgs); err != nil { | ||
return fmt.Errorf("%s %s", color.RedString("⨯"), err.Error()) | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package sbom | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/debricked/cli/internal/cmd/report/testdata" | ||
"github.com/debricked/cli/internal/report" | ||
"github.com/spf13/viper" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewSBOMCmd(t *testing.T) { | ||
var r report.IReporter | ||
cmd := NewSBOMCmd(r) | ||
commands := cmd.Commands() | ||
nbrOfCommands := 0 | ||
assert.Len(t, commands, nbrOfCommands) | ||
|
||
viperKeys := viper.AllKeys() | ||
flags := cmd.Flags() | ||
flagAssertions := map[string]string{ | ||
CommitFlag: "c", | ||
RepositorylFlag: "r", | ||
} | ||
for name, shorthand := range flagAssertions { | ||
flag := flags.Lookup(name) | ||
assert.NotNil(t, flag) | ||
assert.Equalf(t, shorthand, flag.Shorthand, "failed to assert that %s flag shorthand %s was set correctly", name, shorthand) | ||
|
||
match := false | ||
for _, key := range viperKeys { | ||
if key == name { | ||
match = true | ||
} | ||
} | ||
assert.Truef(t, match, "failed to assert that %s was present", name) | ||
} | ||
} | ||
|
||
func TestRunEError(t *testing.T) { | ||
reporterMock := testdata.NewReporterMock() | ||
reporterMock.SetError(errors.New("")) | ||
runeE := RunE(reporterMock) | ||
|
||
err := runeE(nil, nil) | ||
|
||
assert.ErrorContains(t, err, "⨯") | ||
} | ||
|
||
func TestRunE(t *testing.T) { | ||
reporterMock := testdata.NewReporterMock() | ||
runeE := RunE(reporterMock) | ||
|
||
err := runeE(nil, nil) | ||
|
||
assert.NoError(t, err) | ||
} | ||
|
||
func TestPreRun(t *testing.T) { | ||
cmd := NewSBOMCmd(nil) | ||
cmd.PreRun(cmd, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package sbom | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/debricked/cli/internal/client" | ||
"github.com/debricked/cli/internal/report" | ||
) | ||
|
||
var ( | ||
ErrHandleArgs = errors.New("failed to handle args") | ||
ErrSubscription = errors.New("premium feature. Please visit https://debricked.com/pricing/ for more info") | ||
) | ||
|
||
type generateSbom struct { | ||
Format string `json:"format"` | ||
RepositoryID string `json:"repositoryId"` | ||
IntegrationName string `json:"integrationName"` | ||
CommitID string `json:"commitId"` | ||
Email string `json:"email"` | ||
Branch string `json:"branch"` | ||
Locale string `json:"locale"` | ||
Licenses bool `json:licenses` | ||
Vulnerabilities bool `json:"vulnerabilities"` | ||
SendEmail bool `json:sendEmail` | ||
VulnerabilityStatuses []string `json:vulnerabilityStatuses` | ||
} | ||
|
||
type generateSbomResponse struct { | ||
Message string `json:message` | ||
ReportUUID string `json:reportUuid` | ||
Notes []string `json:notes` | ||
} | ||
|
||
type OrderArgs struct { | ||
RepositoryID string | ||
CommitID string | ||
Token string | ||
} | ||
|
||
type Reporter struct { | ||
DebClient client.IDebClient | ||
} | ||
|
||
func (r Reporter) Order(args report.IOrderArgs) error { | ||
orderArgs, ok := args.(OrderArgs) | ||
if !ok { | ||
return ErrHandleArgs | ||
} | ||
uuid, err := r.generate(orderArgs.CommitID, orderArgs.RepositoryID) | ||
if err != nil { | ||
return err | ||
} | ||
sbomJSON, err := r.download(uuid) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Print(sbomJSON) | ||
|
||
return nil | ||
|
||
} | ||
|
||
func (r Reporter) generate(commitID, repositoryID string) (string, error) { | ||
// Tries to start generating an SBOM and returns the UUID for the report | ||
body, err := json.Marshal(generateSbom{ | ||
Format: "CycloneDX", | ||
RepositoryID: repositoryID, | ||
CommitID: commitID, | ||
Email: "", | ||
Branch: "master", // Probably current branch or specified | ||
Locale: "en", | ||
Vulnerabilities: false, | ||
Licenses: false, | ||
SendEmail: false, | ||
VulnerabilityStatuses: []string{"vulnerable", "unexamined", "paused", "snoozed"}, | ||
}) | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
response, err := (r.DebClient).Post( | ||
"/api/1.0/open/sbom/generate", | ||
"application/json", | ||
bytes.NewBuffer(body), | ||
0, | ||
) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer response.Body.Close() | ||
if response.StatusCode == http.StatusPaymentRequired { | ||
return "", ErrSubscription | ||
} else if response.StatusCode != http.StatusOK { | ||
return "", fmt.Errorf("failed to initialize SBOM generation due to status code %d", response.StatusCode) | ||
} else { | ||
fmt.Println("Successfully initialized SBOM generation") | ||
} | ||
generateSbomResponseJSON, err := io.ReadAll(response.Body) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
var generateSbomResponse generateSbomResponse | ||
err = json.Unmarshal(generateSbomResponseJSON, &generateSbomResponse) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return generateSbomResponse.ReportUUID, nil | ||
} | ||
|
||
func (r Reporter) download(uuid string) (string, error) { | ||
uri := fmt.Sprintf("/api/1.0/open/sbom/download?reportUuid=%s", uuid) | ||
fmt.Println("Trying to download SBOM, will wait if not yet ready (could take up to 1 minute)") | ||
for { // poll download status until completion | ||
res, err := (r.DebClient).Get(uri, "application/json") | ||
if err != nil { | ||
return "", err | ||
} | ||
switch statusCode := res.StatusCode; statusCode { | ||
case http.StatusOK: | ||
data, _ := io.ReadAll(res.Body) | ||
defer res.Body.Close() | ||
|
||
return string(data), nil | ||
case http.StatusCreated: | ||
return "", errors.New("polling failed due to too long queue times") | ||
case http.StatusAccepted: | ||
time.Sleep(3000 * time.Millisecond) | ||
default: | ||
return "", fmt.Errorf("download failed with status code %d", res.StatusCode) | ||
} | ||
} | ||
} |
Oops, something went wrong.