Skip to content

Commit

Permalink
feat(instrumentation): integrate analytics report workflow CLI-302 (#…
Browse files Browse the repository at this point in the history
…5259)

* feat(instrumentation): integrate analytics report workflow

* fix(test): fixed tests

* fix: make linter happy

* fix: refine test assertions
  • Loading branch information
mgyorke authored May 31, 2024
1 parent ebe0e2c commit 7d32112
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 41 deletions.
56 changes: 43 additions & 13 deletions cliv2/cmd/cliv2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,35 @@ import (
"strings"
"time"

"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/snyk/cli-extension-dep-graph/pkg/depgraph"
"github.com/snyk/cli-extension-iac-rules/iacrules"
"github.com/snyk/cli-extension-sbom/pkg/sbom"
"github.com/snyk/cli/cliv2/internal/cliv2"
"github.com/snyk/cli/cliv2/internal/constants"
"github.com/snyk/container-cli/pkg/container"
"github.com/snyk/go-application-framework/pkg/analytics"
"github.com/snyk/go-application-framework/pkg/app"
"github.com/snyk/go-application-framework/pkg/auth"
"github.com/snyk/go-application-framework/pkg/configuration"
"github.com/snyk/go-application-framework/pkg/instrumentation"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

localworkflows "github.com/snyk/go-application-framework/pkg/local_workflows"
"github.com/snyk/go-application-framework/pkg/local_workflows/content_type"
"github.com/snyk/go-application-framework/pkg/local_workflows/json_schemas"
"github.com/snyk/go-application-framework/pkg/networking"
"github.com/snyk/go-application-framework/pkg/runtimeinfo"
"github.com/snyk/go-application-framework/pkg/ui"
"github.com/snyk/go-application-framework/pkg/utils"
"github.com/snyk/go-application-framework/pkg/workflow"
"github.com/snyk/go-httpauth/pkg/httpauth"
"github.com/snyk/snyk-iac-capture/pkg/capture"
snykls "github.com/snyk/snyk-ls/ls_extension"

"github.com/snyk/go-application-framework/pkg/instrumentation"
"github.com/snyk/go-application-framework/pkg/ui"
snykls "github.com/snyk/snyk-ls/ls_extension"

"github.com/snyk/cli/cliv2/internal/cliv2"
"github.com/snyk/cli/cliv2/internal/constants"
cli_errors "github.com/snyk/cli/cliv2/internal/errors"
"github.com/snyk/cli/cliv2/pkg/basic_workflows"
)
Expand Down Expand Up @@ -251,15 +251,45 @@ func sendAnalytics(analytics analytics.Analytics, debugLogger *zerolog.Logger) {
}
}

func sendInstrumentation(instrumentor analytics.InstrumentationCollector, logger *zerolog.Logger) {
// TODO: actually send data once CLI-303 is implemented
func sendInstrumentation(eng workflow.Engine, instrumentor analytics.InstrumentationCollector, logger *zerolog.Logger) {
// Avoid duplicate data to be send for IDE integrations that use the CLI
integration := globalConfiguration.GetString(configuration.INTEGRATION_NAME)
if utils.IsSnykIde(integration) {
logger.Print("Called from IDE, not sending instrumentation")
return
}

logger.Print("Sending Instrumentation")
data, err := analytics.GetV2InstrumentationObject(instrumentor)
if err != nil {
logger.Err(err).Msg("Failed to derive data object.")
logger.Err(err).Msg("Failed to derive data object")
}

v2InstrumentationData := utils.ValueOf(json.Marshal(data))
logger.Trace().Msgf("Instrumentation: %v", string(v2InstrumentationData))

inputData := workflow.NewData(
workflow.NewTypeIdentifier(localworkflows.WORKFLOWID_REPORT_ANALYTICS, "reportAnalytics"),
"application/json",
v2InstrumentationData,
)

logger.Trace().Msg("Reporting instrumentation data")
localConfiguration := globalConfiguration.Clone()
// the report analytics workflow needs --experimental to run
// we pass the flag here so that we report at every interaction
localConfiguration.Set(configuration.FLAG_EXPERIMENTAL, true)
_, err = eng.InvokeWithInputAndConfig(
localworkflows.WORKFLOWID_REPORT_ANALYTICS,
[]workflow.Data{inputData},
localConfiguration,
)

if err != nil {
logger.Err(err).Msg("Failed to send Instrumentation")
} else {
logger.Print("Instrumentation successfully sent")
}
}

func help(_ *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -510,7 +540,7 @@ func MainWithErrorCode() int {
if !globalConfiguration.GetBool(configuration.ANALYTICS_DISABLED) {
defer sendAnalytics(cliAnalytics, globalLogger)
}
defer sendInstrumentation(cliAnalytics.GetInstrumentation(), globalLogger)
defer sendInstrumentation(globalEngine, cliAnalytics.GetInstrumentation(), globalLogger)

setTimeout(globalConfiguration, func() {
os.Exit(constants.SNYK_EXIT_CODE_EX_UNAVAILABLE)
Expand Down
35 changes: 23 additions & 12 deletions test/jest/acceptance/snyk-test/all-projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,11 @@ describe('snyk test --all-projects (mocked server only)', () => {
);

expect(code).toEqual(1);
const req = server.popRequest();
expect(req.query.ignorePolicy).toBeTruthy(); // should request to ignore the policy
const requests = server.getRequests().filter((req: any) => {
return req.query.ignorePolicy;
});

expect(requests).toHaveLength(1);
expect(stdout).toMatch(
'Tested 7 dependencies for known vulnerabilities, found 5 vulnerabilities, 6 vulnerable paths.',
);
Expand All @@ -169,7 +171,10 @@ describe('snyk test --all-projects (mocked server only)', () => {
? 'vulnerable\\package-lock.json'
: 'vulnerable/package-lock.json';

const backendRequests = server.popRequests(2);
const backendRequests = server.getRequests().filter((req: any) => {
return req.url.includes('/api/v1/test-dep-graph');
});

expect(backendRequests).toHaveLength(2);
let policyCount = 0;
backendRequests.forEach((req) => {
Expand Down Expand Up @@ -214,10 +219,12 @@ describe('snyk test --all-projects (mocked server only)', () => {
env,
});

const backendRequests = server.popRequests(1);
expect(backendRequests).toHaveLength(1);
const backendRequests = server.getRequests().filter((req: any) => {
return req.url.includes('/api/v1/test');
});

backendRequests.forEach((req) => {
expect(backendRequests).toHaveLength(1);
backendRequests.forEach((req: any) => {
expect(req.method).toEqual('POST');
expect(req.headers['x-snyk-cli-version']).not.toBeUndefined();
expect(req.url).toMatch('/api/v1/test');
Expand All @@ -239,10 +246,12 @@ describe('snyk test --all-projects (mocked server only)', () => {
env,
});

const backendRequests = server.popRequests(1);
expect(backendRequests).toHaveLength(1);
const backendRequests = server.getRequests().filter((req: any) => {
return req.url.includes('/api/v1/test');
});

backendRequests.forEach((req) => {
expect(backendRequests).toHaveLength(6);
backendRequests.forEach((req: any) => {
expect(req.method).toEqual('POST');
expect(req.headers['x-snyk-cli-version']).not.toBeUndefined();
expect(req.url).toMatch('/api/v1/test');
Expand All @@ -265,10 +274,12 @@ describe('snyk test --all-projects (mocked server only)', () => {
env,
});

const backendRequests = server.popRequests(1);
expect(backendRequests).toHaveLength(1);
const backendRequests = server.getRequests().filter((req: any) => {
return req.url.includes('/api/v1/test');
});

backendRequests.forEach((req) => {
expect(backendRequests).toHaveLength(10);
backendRequests.forEach((req: any) => {
expect(req.method).toEqual('POST');
expect(req.headers['x-snyk-cli-version']).not.toBeUndefined();
expect(req.url).toMatch('/api/v1/test');
Expand Down
38 changes: 22 additions & 16 deletions test/jest/acceptance/snyk-test/protect-upgrade-notification.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ describe('analytics module', () => {
);
expect(stdout).toContain(project.path('package.json'));

// in this case an extra analytics event is being sent, which needs to be dropped
server.popRequest();
const requests = server.getRequests().filter((req: any) => {
return JSON.stringify(req.body).includes('upgradable-snyk-protect-paths');
});

const lastRequest = server.popRequest();
expect(lastRequest).toMatchObject({
expect(requests).toHaveLength(1);
expect(requests[0]).toMatchObject({
query: {},
body: {
data: {
Expand Down Expand Up @@ -86,11 +87,12 @@ describe('analytics module', () => {
);
expect(stdout).toContain(project.path('package.json'));

// in this case an extra analytics event is being sent, which needs to be dropped
server.popRequest();
const requests = server.getRequests().filter((req: any) => {
return JSON.stringify(req.body).includes('upgradable-snyk-protect-paths');
});

const lastRequest = server.popRequest();
expect(lastRequest).toMatchObject({
expect(requests).toHaveLength(1);
expect(requests[0]).toMatchObject({
query: {},
body: {
data: {
Expand Down Expand Up @@ -135,11 +137,13 @@ describe('analytics module', () => {
project.path('with-package-json-without-snyk-dep/package.json'),
);

// in this case an extra analytics event is being sent, which needs to be dropped
server.popRequest();
const requests = server.getRequests().filter((req: any) => {
return JSON.stringify(req.body).includes('upgradable-snyk-protect-paths');
});

const lastRequest = server.popRequest();
expect(lastRequest).toMatchObject({
expect(requests[0].url).toEqual('/api/v1/analytics/cli');
expect(requests).toHaveLength(1);
expect(requests[0]).toMatchObject({
query: {},
body: {
data: {
Expand Down Expand Up @@ -168,11 +172,13 @@ describe('analytics module', () => {
);
expect(stdout).not.toContain(project.path('package.json'));

// in this case an extra analytics event is being sent, which needs to be dropped
server.popRequest();
const requests = server.getRequests().filter((req: any) => {
return JSON.stringify(req.body).includes('upgradable-snyk-protect-paths');
});

const lastRequest = server.popRequest();
expect(lastRequest).toMatchObject({
expect(requests[0].url).toEqual('/api/v1/analytics/cli');
expect(requests).toHaveLength(1);
expect(requests[0]).toMatchObject({
query: {},
body: {
data: {
Expand Down

0 comments on commit 7d32112

Please sign in to comment.