Skip to content
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

feat(instrumentation): integrate analytics report workflow CLI-302 #5259

Merged
merged 4 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) => {
PeterSchafer marked this conversation as resolved.
Show resolved Hide resolved
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
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
Loading