Skip to content

Commit

Permalink
feat: telemetry data collection in cli (#44)
Browse files Browse the repository at this point in the history
* telemetry

* Tests

* telemetry data

* Addressing review comments

* Addressing review comments

* Addressing review comments

* Addressing review comments

* Prompt

* client flag

* 2 new properties

* removing crda key recording

* linter fixes

* mod and go sum

* test commit

* removing test commit
  • Loading branch information
Deepak Sharma authored Apr 21, 2021
1 parent 252ab6f commit caeee8f
Show file tree
Hide file tree
Showing 19 changed files with 736 additions and 104 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ coverage.txt
target/

# Programatically generated files
generate_pylist.py
generate_pylist.py

node_modules/
5 changes: 5 additions & 0 deletions analyses/driver/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ type GetResponseType struct {
StackID string `json:"external_request_id"`
}

// ErrorResponse is a struct to unmarshal API Error response
type ErrorResponse struct {
Error string `json:"error"`
}

// ReadManifestResponse is arg type of readManifest func
type ReadManifestResponse struct {
DepsTreePath string `json:"manifest,omitempty"`
Expand Down
72 changes: 47 additions & 25 deletions analyses/stackanalyses/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package stackanalyses

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -37,32 +39,38 @@ const (
)

//StackAnalyses is main controller function for analyse command. This function is responsible for all communications between cmd and custom packages.
func StackAnalyses(requestParams driver.RequestType, jsonOut bool, verboseOut bool) bool {
func StackAnalyses(ctx context.Context, requestParams driver.RequestType, jsonOut bool, verboseOut bool) (bool, error) {
log.Debug().Msgf("Executing StackAnalyses.")
var hasVul bool
matcher, err := GetMatcher(requestParams.RawManifestFile)
if err != nil {
log.Fatal().Msgf(err.Error())
return hasVul, err
}
mc := NewController(matcher)
mc.fileStats = mc.buildFileStats(requestParams.RawManifestFile)
postResponse := mc.postRequest(requestParams, mc.fileStats.DepsTreePath)
getResponse := mc.getRequest(requestParams, postResponse)
postResponse, err := mc.postRequest(requestParams, mc.fileStats.DepsTreePath)
if err != nil {
return hasVul, err
}
getResponse, err := mc.getRequest(requestParams, postResponse)
if err != nil {
return hasVul, err
}
verboseEligible := getResponse.RegistrationStatus == RegisteredStatus
showVerboseMsg := verboseOut && !verboseEligible

if verboseOut && verboseEligible {
hasVul = verbose.ProcessVerbose(getResponse, jsonOut)
hasVul = verbose.ProcessVerbose(ctx, getResponse, jsonOut)
} else {
hasVul = summary.ProcessSummary(getResponse, jsonOut, showVerboseMsg)
hasVul = summary.ProcessSummary(ctx, getResponse, jsonOut, showVerboseMsg)
}

log.Debug().Msgf("Success StackAnalyses.")
return hasVul
return hasVul, nil
}

// postRequest performs Stack Analyses POST Request to CRDA server.
func (mc *Controller) postRequest(requestParams driver.RequestType, filePath string) driver.PostResponseType {
func (mc *Controller) postRequest(requestParams driver.RequestType, filePath string) (*driver.PostResponseType, error) {
log.Debug().Msgf("Executing: postRequest.")
manifest := &bytes.Buffer{}
requestData := utils.HTTPRequestType{
Expand All @@ -75,34 +83,37 @@ func (mc *Controller) postRequest(requestParams driver.RequestType, filePath str
writer := multipart.NewWriter(manifest)
fd, err := os.Open(filePath)
if err != nil {
log.Fatal().Err(err).Msgf(err.Error())
return nil, err
}
defer fd.Close()

fw, err := writer.CreateFormFile("manifest", mc.m.DepsTreeFileName())
if err != nil {
log.Fatal().Err(err).Msgf(err.Error())
return nil, err
}
_, err = io.Copy(fw, fd)
if err != nil {
log.Fatal().Err(err).Msgf(err.Error())
return nil, err
}
_ = writer.WriteField("ecosystem", mc.m.Ecosystem())
_ = writer.WriteField("file_path", "/tmp/bin")
err = writer.Close()
if err != nil {
log.Fatal().Err(err).Msgf("Error closing Buffer Writer in Stack Analyses Request.")
return nil, errors.New("error closing Buffer Writer in Stack Analyses Request")
}
log.Debug().Msgf("Hitting: Stack Analyses Post API.")
apiResponse := utils.HTTPRequestMultipart(requestData, writer, manifest)
body := mc.validatePostResponse(apiResponse)
body, err := mc.validatePostResponse(apiResponse)
if err != nil {
return nil, err
}
log.Debug().Msgf("Got Stack Analyses Post Response Stack Id: %s", body.ID)
log.Debug().Msgf("Success: postRequest.")
return body
return body, nil
}

// getRequest performs Stack Analyses GET Request to CRDA Server.
func (mc *Controller) getRequest(requestParams driver.RequestType, postResponse driver.PostResponseType) driver.GetResponseType {
func (mc *Controller) getRequest(requestParams driver.RequestType, postResponse *driver.PostResponseType) (*driver.GetResponseType, error) {
log.Debug().Msgf("Executing: getRequest.")
polling := &backoff.Backoff{
Min: 5 * time.Second,
Expand All @@ -129,34 +140,44 @@ func (mc *Controller) getRequest(requestParams driver.RequestType, postResponse
}
log.Debug().Msgf("Retrying...")
}
body := mc.validateGetResponse(apiResponse)
return body
body, err := mc.validateGetResponse(apiResponse)
if err != nil {
return nil, err
}
return body, nil
}

// validatePostResponse validates Stack Analyses POST API Response.
func (mc *Controller) validatePostResponse(apiResponse *http.Response) driver.PostResponseType {
func (mc *Controller) validatePostResponse(apiResponse *http.Response) (*driver.PostResponseType, error) {
log.Debug().Msgf("Executing validatePostResponse.")
var body driver.PostResponseType
err := json.NewDecoder(apiResponse.Body).Decode(&body)
if err != nil {
return nil, err
}
if apiResponse.StatusCode != http.StatusOK {
log.Debug().Msgf("Status from Server: %d", apiResponse.StatusCode)
log.Fatal().Err(err).Msgf("Stack Analyses Post Request Failed. Please retry after sometime. If issue persists, Please raise at https://github.com/fabric8-analytics/cli-tools/issues.")
log.Error().Msgf("Stack Analyses Post Request Failed. Please retry after sometime. If issue persists, Please raise at https://github.com/fabric8-analytics/cli-tools/issues.\"")
return nil, fmt.Errorf("SA POST Request Failed. status code: %d", apiResponse.StatusCode)
}
log.Debug().Msgf("Success validatePostResponse.")
return body
return &body, nil
}

// validateGetResponse validates Stack Analyses GET API Response.
func (mc *Controller) validateGetResponse(apiResponse *http.Response) driver.GetResponseType {
func (mc *Controller) validateGetResponse(apiResponse *http.Response) (*driver.GetResponseType, error) {
log.Debug().Msgf("Executing validateGetResponse.")
var body driver.GetResponseType
err := json.NewDecoder(apiResponse.Body).Decode(&body)
if apiResponse.StatusCode != http.StatusOK {
log.Debug().Msgf("Status from Server: %d", apiResponse.StatusCode)
log.Fatal().Err(err).Msgf("Stack Analyses Request Failed. Please retry after sometime. If issue persists, Please raise at https://github.com/fabric8-analytics/cli-tools/issues.")
var body driver.ErrorResponse
json.NewDecoder(apiResponse.Body).Decode(&body)
log.Error().Msgf("Stack Analyses Get Request Failed. Please retry after sometime. If issue persists, Please raise at https://github.com/fabric8-analytics/cli-tools/issues.\"")
return nil, fmt.Errorf("SA GET Request Failed. status code: %d", apiResponse.StatusCode)
}
log.Debug().Msgf("Success validateGetResponse.")
return body
return &body, err
}

// NewController is a constructor for a Controller
Expand Down Expand Up @@ -187,15 +208,16 @@ func GetMatcher(manifestFile string) (driver.StackAnalysisInterface, error) {
func (mc *Controller) buildFileStats(manifestFile string) *driver.ReadManifestResponse {
stats := &driver.ReadManifestResponse{
Ecosystem: mc.m.Ecosystem(),
RawFileName: mc.getManifestName(manifestFile),
RawFileName: GetManifestName(manifestFile),
RawFilePath: manifestFile,
DepsTreePath: mc.m.GeneratorDependencyTree(manifestFile),
DepsTreeFileName: mc.m.DepsTreeFileName(),
}
return stats
}

func (mc *Controller) getManifestName(manifestFile string) string {
// GetManifestName extracts manifest name from user input path
func GetManifestName(manifestFile string) string {
stats, err := os.Stat(manifestFile)
if err != nil {
log.Fatal().Err(err).Msgf("Error fetching manifest name.")
Expand Down
9 changes: 6 additions & 3 deletions analyses/summary/helper_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package summary

import (
"context"
"github.com/fabric8-analytics/cli-tools/pkg/telemetry"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/fabric8-analytics/cli-tools/analyses/driver"
)

func data() driver.GetResponseType {
func data() *driver.GetResponseType {
var GetResponse = &driver.GetResponseType{
AnalysedDeps: []driver.AnalysedDepsType{
{
Expand All @@ -26,11 +28,12 @@ func data() driver.GetResponseType {
},
StackID: "123456789",
}
return *GetResponse
return GetResponse
}

func TestProcessSummary(t *testing.T) {
got := ProcessSummary(data(), false, false)
var ctx = telemetry.NewContext(context.Background())
got := ProcessSummary(ctx, data(), false, false)
if got != true {
t.Errorf("Error in ProcessSummary.")
}
Expand Down
7 changes: 5 additions & 2 deletions analyses/summary/helpers.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package summary

import (
"context"
"encoding/json"
"fmt"
"github.com/fabric8-analytics/cli-tools/pkg/telemetry"
"os"

"github.com/fatih/color"
Expand All @@ -13,18 +15,19 @@ import (
)

// ProcessSummary processes summary results, return true if Vul found
func ProcessSummary(analysedResult driver.GetResponseType, jsonOut bool, showVerboseMsg bool) bool {
func ProcessSummary(ctx context.Context, analysedResult *driver.GetResponseType, jsonOut bool, showVerboseMsg bool) bool {
out := getResultSummary(analysedResult)
if jsonOut {
outputSummaryJSON(out)
} else {
outputSummaryPlain(out, showVerboseMsg)
}
telemetry.SetVulnerability(ctx, out.TotalVulnerabilities)
return out.TotalVulnerabilities > 0
}

// GetResultSummary processes result Summary
func getResultSummary(analysedResult driver.GetResponseType) *StackSummary {
func getResultSummary(analysedResult *driver.GetResponseType) *StackSummary {
totalDepsScanned := len(analysedResult.AnalysedDeps)
data := processVulnerabilities(analysedResult.AnalysedDeps)
out := &StackSummary{
Expand Down
10 changes: 7 additions & 3 deletions analyses/verbose/helper.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package verbose

import (
"context"
"encoding/json"
"fmt"
"github.com/fabric8-analytics/cli-tools/pkg/telemetry"
"os"
"sort"

Expand All @@ -22,18 +24,20 @@ var cusColor = &CustomColors{
}

// ProcessVerbose processes verbose results and decides STDOUT format
func ProcessVerbose(analysedResult driver.GetResponseType, jsonOut bool) bool {
func ProcessVerbose(ctx context.Context, analysedResult *driver.GetResponseType, jsonOut bool) bool {
out := getVerboseResult(analysedResult)
if jsonOut {
outputVerboseJSON(out)
} else {
outputVerbosePlain(out)
}
return out.TotalDirectVulnerabilities+out.TotalTransitiveVulnerabilities > 0
TotalVul := out.TotalDirectVulnerabilities + out.TotalTransitiveVulnerabilities
telemetry.SetVulnerability(ctx, TotalVul)
return TotalVul > 0
}

// getVerboseResult prepares verbose struct
func getVerboseResult(analysedResult driver.GetResponseType) *StackVerbose {
func getVerboseResult(analysedResult *driver.GetResponseType) *StackVerbose {
data := processVulnerabilities(analysedResult.AnalysedDeps)
out := &StackVerbose{
Dependencies: data.AnalysedDependencies,
Expand Down
9 changes: 6 additions & 3 deletions analyses/verbose/helper_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package verbose

import (
"context"
"encoding/json"
"github.com/fabric8-analytics/cli-tools/pkg/telemetry"
"io/ioutil"
"testing"

Expand All @@ -10,12 +12,12 @@ import (
"github.com/fabric8-analytics/cli-tools/analyses/driver"
)

func data() driver.GetResponseType {
func data() *driver.GetResponseType {
var body driver.GetResponseType
// json.NewDecoder(apiResponse.Body).Decode(&body)
plan, _ := ioutil.ReadFile("testdata/getresponse.json")
json.Unmarshal(plan, &body)
return body
return &body
}
func verboseData() *StackVerbose {
var body StackVerbose
Expand All @@ -25,7 +27,8 @@ func verboseData() *StackVerbose {
}

func TestProcessSummary(t *testing.T) {
got := ProcessVerbose(data(), false)
var ctx = telemetry.NewContext(context.Background())
got := ProcessVerbose(ctx, data(), false)
if got != true {
t.Errorf("Error in ProcessSummary.")
}
Expand Down
Loading

0 comments on commit caeee8f

Please sign in to comment.