diff --git a/.github/workflows/debricked.yml b/.github/workflows/debricked.yml index ed016216..d0b7997e 100644 --- a/.github/workflows/debricked.yml +++ b/.github/workflows/debricked.yml @@ -1,6 +1,10 @@ name: Debricked Automations -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: jobs: vulnerabilities-scan: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80ba8e6a..c2859f1f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,7 @@ jobs: - name: Test coverage run: bash scripts/check_coverage.sh + if: always() env: TEST_COVERAGE_THRESHOLD: 90 diff --git a/pkg/client/client.go b/pkg/client/client.go index db942ba1..923ec9a4 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -39,6 +39,6 @@ func (debClient *DebClient) Post(uri string, contentType string, body *bytes.Buf } // Get makes a GET request to one of Debricked's API endpoints -func (debClient *DebClient) Get(uri string) (*http.Response, error) { - return get(uri, debClient, true) +func (debClient *DebClient) Get(uri string, format string) (*http.Response, error) { + return get(uri, debClient, true, format) } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index aa933215..18ec717b 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -80,7 +80,7 @@ func TestNewDebClientWithWithURI(t *testing.T) { func TestClientUnauthorized(t *testing.T) { setUp(false) - _, err := client.Get("/api/1.0/open/user-profile/is-admin") + _, err := client.Get("/api/1.0/open/user-profile/is-admin", "application/json") if err == nil { t.Error("failed to assert client error") } @@ -91,7 +91,7 @@ func TestClientUnauthorized(t *testing.T) { func TestGet(t *testing.T) { setUp(true) - res, err := client.Get("/api/1.0/open/user-profile/is-admin") + res, err := client.Get("/api/1.0/open/user-profile/is-admin", "application/json") if err != nil { t.Fatal("failed to assert that no client error occurred. Error: ", err.Error()) } diff --git a/pkg/client/request.go b/pkg/client/request.go index fa7e448c..2d239e8c 100644 --- a/pkg/client/request.go +++ b/pkg/client/request.go @@ -10,21 +10,21 @@ import ( "net/http" ) -func get(uri string, debClient *DebClient, retry bool) (*http.Response, error) { - request, err := newRequest("GET", *debClient.host+uri, debClient.jwtToken, nil) +func get(uri string, debClient *DebClient, retry bool, format string) (*http.Response, error) { + request, err := newRequest("GET", *debClient.host+uri, debClient.jwtToken, format, nil) if err != nil { return nil, err } res, _ := debClient.httpClient.Do(request) req := func() (*http.Response, error) { - return get(uri, debClient, false) + return get(uri, debClient, false, format) } return interpret(res, req, debClient, retry) } func post(uri string, debClient *DebClient, contentType string, body *bytes.Buffer, retry bool) (*http.Response, error) { - request, err := newRequest("POST", *debClient.host+uri, debClient.jwtToken, body) + request, err := newRequest("POST", *debClient.host+uri, debClient.jwtToken, "application/json", body) if err != nil { return nil, err } @@ -40,12 +40,12 @@ func post(uri string, debClient *DebClient, contentType string, body *bytes.Buff } // newRequest creates a new HTTP request with necessary headers added -func newRequest(method string, url string, jwtToken string, body io.Reader) (*http.Request, error) { +func newRequest(method string, url string, jwtToken string, format string, body io.Reader) (*http.Request, error) { req, err := http.NewRequest(method, url, body) if err != nil { return nil, err } - req.Header.Add("Accept", `application/json`) + req.Header.Add("Accept", format) req.Header.Add("Authorization", "Bearer "+jwtToken) return req, nil diff --git a/pkg/cmd/check/check.go b/pkg/cmd/check/check.go index c538eeea..da62ae4c 100644 --- a/pkg/cmd/check/check.go +++ b/pkg/cmd/check/check.go @@ -2,8 +2,13 @@ package check import ( "debricked/pkg/client" + "encoding/json" + "errors" + "fmt" "github.com/spf13/cobra" + "io" "log" + "net/http" ) var debClient *client.DebClient @@ -32,3 +37,34 @@ func check(hash string) error { return nil } + +type latestScan struct { + Id *int `json:"id"` + Date *int `json:"date"` + Url *string `json:"commitUrl"` +} + +func getScanId(commitId int) (int, error) { + uri := fmt.Sprintf("/api/1.0/open/scan/latest-scan-status?commitId=%d", commitId) + res, err := debClient.Get(uri, "application/json") + if err != nil { + return 0, err + } + + if res.StatusCode != http.StatusOK { + return 0, errors.New(fmt.Sprintf("No scan was found for commit")) + } + + body, err := io.ReadAll(res.Body) + var scan map[string]latestScan + err = json.Unmarshal(body, &scan) + if err != nil { + return 0, err + } + id := scan["latestScan"].Id + if id == nil { + return 0, errors.New(fmt.Sprintf("No scan was found for commit")) + } + + return *id, err +} diff --git a/pkg/cmd/report/license/license.go b/pkg/cmd/report/license/license.go new file mode 100644 index 00000000..0912edf4 --- /dev/null +++ b/pkg/cmd/report/license/license.go @@ -0,0 +1,105 @@ +package license + +import ( + "debricked/pkg/client" + "encoding/json" + "errors" + "fmt" + "github.com/fatih/color" + "github.com/spf13/cobra" + "io" + "net/http" +) + +var debClient *client.DebClient + +var email string +var commitHash string + +func NewLicenseCmd(debrickedClient *client.DebClient) *cobra.Command { + debClient = debrickedClient + cmd := &cobra.Command{ + Use: "license", + Short: "Generate license report", + Long: `Generate license report from a commit hash. +This is a premium feature. Please visit https://debricked.com/pricing/ for more info. +The finished report will be sent to the specified email address.`, + RunE: run, + } + + cmd.Flags().StringVarP(&email, "email", "e", "", "The email address that the report will be sent to") + _ = cmd.MarkFlagRequired("email") + + cmd.Flags().StringVarP(&commitHash, "commit", "c", "", "commit hash") + _ = cmd.MarkFlagRequired("commit") + + return cmd +} + +func run(_ *cobra.Command, _ []string) error { + if err := report(); err != nil { + return errors.New(fmt.Sprintf("%s %s\n", color.RedString("⨯"), err.Error())) + } + + fmt.Printf("%s Successfully ordered license report\n", color.GreenString("✔")) + + return nil +} + +func report() error { + commitId, err := getCommitId(commitHash) + if err != nil { + return err + } + + return orderReport(commitId) +} + +func orderReport(commitId int) error { + uri := fmt.Sprintf("/api/1.0/open/licenses/get-licenses?order=asc&sortColumn=name&generateExcel=1&commitId=%d&email=%s", commitId, email) + res, err := debClient.Get(uri, "application/json") + if err != nil { + return err + } + if res.StatusCode == http.StatusForbidden { + return errors.New("premium feature. Please visit https://debricked.com/pricing/ for more info") + } + + if res.StatusCode != http.StatusOK { + return errors.New(fmt.Sprintf("failed to order report. Status code: %d", res.StatusCode)) + } + + return nil +} + +type commit struct { + FileIds []int `json:"uploaded_programs_file_ids"` + Id int `json:"id"` + Name string `json:"name"` + ReleaseData string `json:"release_date"` +} + +func getCommitId(hash string) (int, error) { + uri := fmt.Sprintf("/api/1.0/releases/by/name?name=%s", hash) + res, err := debClient.Get(uri, "application/json") + if err != nil { + return 0, err + } + + if res.StatusCode == http.StatusForbidden { + return 0, errors.New("premium feature. Please visit https://debricked.com/pricing/ for more info") + } + + if res.StatusCode != http.StatusOK { + return 0, errors.New(fmt.Sprintf("No commit was found with the name %s", hash)) + } + + body, err := io.ReadAll(res.Body) + var commits []commit + err = json.Unmarshal(body, &commits) + if len(commits) == 0 { + return 0, errors.New(fmt.Sprintf("No commit was found with the name %s", hash)) + } + + return commits[0].Id, err +} diff --git a/pkg/cmd/report/license/license_test.go b/pkg/cmd/report/license/license_test.go new file mode 100644 index 00000000..1615c6ff --- /dev/null +++ b/pkg/cmd/report/license/license_test.go @@ -0,0 +1,82 @@ +package license + +import ( + "debricked/pkg/client" + "fmt" + "strings" + "testing" +) + +const validCommit = "84cac1be9931f8bcc8ef59c5544aaac8c5c97c8b" + +func TestNewLicenseCmd(t *testing.T) { + cmd := NewLicenseCmd(client.NewDebClient(nil)) + commands := cmd.Commands() + nbrOfCommands := 0 + if len(commands) != nbrOfCommands { + t.Error(fmt.Sprintf("failed to assert that there were %d sub commands connected", nbrOfCommands)) + } + + flags := cmd.Flags() + flagAssertions := map[string]string{ + "commit": "c", + "email": "e", + } + for name, shorthand := range flagAssertions { + flag := flags.Lookup(name) + if flag == nil { + t.Error(fmt.Sprintf("failed to assert that %s flag was set", name)) + } + if flag.Shorthand != shorthand { + t.Error(fmt.Sprintf("failed to assert that %s flag shorthand %s was set correctly", name, shorthand)) + } + } +} + +func TestRunUnAuthorized(t *testing.T) { + email = "noreply@debricked.com" + commitHash = validCommit + accessToken := "invalid" + debClient = client.NewDebClient(&accessToken) + err := run(nil, nil) + if err == nil { + t.Fatal("failed to assert that an error occurred") + } + if !strings.Contains(err.Error(), "⨯ Unauthorized. Specify access token") { + t.Error("failed to assert error message") + } +} + +func TestRun(t *testing.T) { + email = "noreply@debricked.com" + commitHash = validCommit + debClient = client.NewDebClient(nil) + err := run(nil, nil) + if err != nil { + t.Fatal("failed to assert that no error occurred") + } +} + +func TestReportInvalidCommitHash(t *testing.T) { + email = "noreply@debricked.com" + commitHash = "invalid" + debClient = client.NewDebClient(nil) + err := report() + if err == nil { + t.Fatal("failed to assert that error occurred") + } + if !strings.Contains(err.Error(), "No commit was found with the name invalid") { + t.Error("failed to assert error message") + } +} + +func TestGetCommitId(t *testing.T) { + debClient = client.NewDebClient(nil) + id, err := getCommitId(validCommit) + if err != nil { + t.Fatal("failed to assert that no error occurred") + } + if id < 1 { + t.Error("failed to assert that the commit ID was a positive integer") + } +} diff --git a/pkg/cmd/report/report.go b/pkg/cmd/report/report.go index 53c3364c..728222e0 100644 --- a/pkg/cmd/report/report.go +++ b/pkg/cmd/report/report.go @@ -2,29 +2,22 @@ package report import ( "debricked/pkg/client" - "fmt" + "debricked/pkg/cmd/report/license" "github.com/spf13/cobra" - "log" ) var debClient *client.DebClient -func Report() error { - fmt.Println("Reporting!") - return nil -} - func NewReportCmd(debrickedClient *client.DebClient) *cobra.Command { debClient = debrickedClient - return &cobra.Command{ + cmd := &cobra.Command{ Use: "report", - Short: "Generate license report for upload ID", - Long: "Generate license report for upload ID", - Run: func(cmd *cobra.Command, args []string) { - err := Report() - if err != nil { - log.Fatal(err) - } - }, + Short: "Generate reports", + Long: `Generate reports for a commit. +This is a premium feature. Please visit https://debricked.com/pricing/ for more info.`, } + + cmd.AddCommand(license.NewLicenseCmd(debClient)) + + return cmd } diff --git a/pkg/cmd/report/report_test.go b/pkg/cmd/report/report_test.go index 80c499fb..bb348eb4 100644 --- a/pkg/cmd/report/report_test.go +++ b/pkg/cmd/report/report_test.go @@ -1 +1,16 @@ package report + +import ( + "debricked/pkg/client" + "fmt" + "testing" +) + +func TestNewReportCmd(t *testing.T) { + cmd := NewReportCmd(client.NewDebClient(nil)) + commands := cmd.Commands() + nbrOfCommands := 1 + if len(commands) != nbrOfCommands { + t.Error(fmt.Sprintf("failed to assert that there were %d sub commands connected", nbrOfCommands)) + } +} diff --git a/pkg/cmd/scan/batch.go b/pkg/cmd/scan/batch.go index bbbfcd92..b1779fe3 100644 --- a/pkg/cmd/scan/batch.go +++ b/pkg/cmd/scan/batch.go @@ -161,7 +161,7 @@ func (uploadBatch *uploadBatch) wait() (*scanStatus, error) { var resultStatus *scanStatus uri := fmt.Sprintf("/api/1.0/open/ci/upload/status?ciUploadId=%s", strconv.Itoa(uploadBatch.ciUploadId)) for !bar.IsFinished() { - res, err := debClient.Get(uri) + res, err := debClient.Get(uri, "application/json") if err != nil { return nil, err } diff --git a/pkg/cmd/scan/scan.go b/pkg/cmd/scan/scan.go index 437f3d43..1cfa6683 100644 --- a/pkg/cmd/scan/scan.go +++ b/pkg/cmd/scan/scan.go @@ -118,7 +118,7 @@ func scan(directoryPath string, gitMetaObject *git.MetaObject, ignoredDirectorie // getSupportedFormats returns all supported dependency file formats func getSupportedFormats() ([]*file.CompiledFormat, error) { - res, err := debClient.Get("/api/1.0/open/files/supported-formats") + res, err := debClient.Get("/api/1.0/open/files/supported-formats", "application/json") if err != nil { return nil, err } diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go index 7c1951d8..8872ae58 100644 --- a/pkg/git/git_test.go +++ b/pkg/git/git_test.go @@ -67,7 +67,7 @@ func TestNewMetaObjectWithRepository(t *testing.T) { if len(newMetaObj.CommitName) == 0 { t.Error("failed to find correct commit", newMetaObj.CommitName) } - if newMetaObj.BranchName != "main" || newMetaObj.DefaultBranchName != "main" { + if len(newMetaObj.BranchName) == 0 || len(newMetaObj.DefaultBranchName) == 0 { t.Error("failed to find correct branch", newMetaObj.BranchName) } if len(newMetaObj.Author) == 0 { @@ -82,7 +82,7 @@ func TestFindBranchName(t *testing.T) { if err != nil { t.Error(err) } - if branch != "main" { + if len(branch) == 0 { t.Error("failed to find correct branch", branch) } } diff --git a/scripts/test.sh b/scripts/test.sh index a7ca9576..b180b708 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,4 +1,4 @@ #!/bin/sh -excludedPackages="debricked/cmd/debricked|debricked/pkg/cmd/report|debricked/pkg/cmd/login|debricked/pkg/cmd/check" +excludedPackages="debricked/cmd/debricked|debricked/pkg/cmd/login|debricked/pkg/cmd/check" go test -cover -coverprofile=coverage.out -v $(go list ./... | grep -Ev $excludedPackages) \ No newline at end of file