diff --git a/cliv2/cmd/cliv2/main.go b/cliv2/cmd/cliv2/main.go index 93838746d7..f00cd8dce6 100644 --- a/cliv2/cmd/cliv2/main.go +++ b/cliv2/cmd/cliv2/main.go @@ -1,11 +1,14 @@ package main import ( + "fmt" "io/ioutil" "log" "os" + "os/exec" "strings" + "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/cli/cliv2/pkg/basic_workflows" @@ -84,11 +87,12 @@ func runCommand(cmd *cobra.Command, args []string) error { func sendAnalytics(analytics analytics.Analytics, debugLogger *log.Logger) { debugLogger.Println("Sending Analytics") - _, err := analytics.Send() - if err == nil { + res, err := analytics.Send() + errorCodeReceived := res != nil && 200 <= res.StatusCode && res.StatusCode < 300 + if err == nil && !errorCodeReceived { debugLogger.Println("Analytics sucessfully send") } else { - debugLogger.Println("Failed to send Analytics", err) + debugLogger.Println("Failed to send Analytics:", err) } } @@ -159,6 +163,33 @@ func prepareRootCommand() *cobra.Command { return &rootCommand } +func doFallback(err error, helped bool) (fallback bool) { + fallback = false + preCondition := err != nil && helpProvided == false + if preCondition { + errString := err.Error() + flagError := strings.Contains(errString, "unknown flag") || + strings.Contains(errString, "flag needs") || + strings.Contains(errString, "invalid argument") + commandError := strings.Contains(errString, "unknown command") + + // filter for known cobra errors, since cobra errors shall trigger a fallback, but not others. + if commandError || flagError { + fallback = true + } + } + + return fallback +} + +func displayError(err error) { + if err != nil { + if _, ok := err.(*exec.ExitError); !ok { + fmt.Println(err) + } + } +} + func MainWithErrorCode() int { var err error @@ -178,6 +209,7 @@ func MainWithErrorCode() int { // initialize the extensions -> they register themselves at the engine engine.AddExtensionInitializer(basic_workflows.Init) + engine.AddExtensionInitializer(sbom.Init) // init engine err = engine.Init() @@ -213,7 +245,7 @@ func MainWithErrorCode() int { err = rootCommand.Execute() // fallback to the legacy cli - if err != nil && helpProvided == false { + if doFallback(err, helpProvided) { debugLogger.Printf("Falling back to legacy cli. (reason: %v)\n", err) err = defaultCmd(nil, []string{}) } @@ -222,6 +254,8 @@ func MainWithErrorCode() int { cliAnalytics.AddError(err) } + displayError(err) + exitCode := cliv2.DeriveExitCode(err) debugLogger.Printf("Exiting with %d\n", exitCode) diff --git a/cliv2/go.mod b/cliv2/go.mod index 2ebc2e9fcb..0cd740a0e7 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -7,7 +7,8 @@ require ( github.com/elazarl/goproxy/ext v0.0.0-20220901064549-fbd10ff4f5a1 github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 - github.com/snyk/go-application-framework v0.0.0-20221201145925-ee0ec4cf2688 + github.com/snyk/cli-extension-sbom v0.0.0-20221212093410-6b474ed1a42a + github.com/snyk/go-application-framework v0.0.0-20221213122015-81ad8dd6311d github.com/snyk/go-httpauth v0.0.0-20220915135832-0edf62cf8cdd github.com/spf13/cobra v1.6.0 github.com/spf13/pflag v1.0.5 diff --git a/cliv2/go.sum b/cliv2/go.sum index caf5e3da31..6dca9c5e15 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -182,8 +182,10 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/snyk/go-application-framework v0.0.0-20221201145925-ee0ec4cf2688 h1:2WOMtq2WSgP/WL9qsQJBIxctf9vuvv+xn/xl6/qgNUY= -github.com/snyk/go-application-framework v0.0.0-20221201145925-ee0ec4cf2688/go.mod h1:cmEA75r0NIy0dyNx5AIKLtczEg6YF0oOPXIKHWiLyWc= +github.com/snyk/cli-extension-sbom v0.0.0-20221212093410-6b474ed1a42a h1:kImXWA4kbwaREeC+kaJ8H0aOukWzpK8K/UzAsExj6MU= +github.com/snyk/cli-extension-sbom v0.0.0-20221212093410-6b474ed1a42a/go.mod h1:ohrrgC94Gx82/cgSiac02JQrsMjFtggvhAvXGuGjDGU= +github.com/snyk/go-application-framework v0.0.0-20221213122015-81ad8dd6311d h1:5//WGQrFXri33xGuLgVEHOsBD0aU2ZHU8JFEGJBBc68= +github.com/snyk/go-application-framework v0.0.0-20221213122015-81ad8dd6311d/go.mod h1:5hLGqObbxLWnZkhn3Xc5PblESjQOfjN509ucQ4dtqz8= github.com/snyk/go-httpauth v0.0.0-20220915135832-0edf62cf8cdd h1:zjDhcQ642rIVI8aIjfG5uVcw+OGotQtX2l9VHe7IqCQ= github.com/snyk/go-httpauth v0.0.0-20220915135832-0edf62cf8cdd/go.mod h1:v6t6wKizOcHXT3p4qKn6Bda7yNIjCQ54Xyl31NjgXkY= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= diff --git a/cliv2/internal/cliv2/cliv2.go b/cliv2/internal/cliv2/cliv2.go index f47a3246cc..29aa733d43 100644 --- a/cliv2/internal/cliv2/cliv2.go +++ b/cliv2/internal/cliv2/cliv2.go @@ -289,7 +289,6 @@ func DeriveExitCode(err error) int { returnCode = exitError.ExitCode() } else { // got an error but it's not an ExitError - fmt.Println(err) returnCode = constants.SNYK_EXIT_CODE_ERROR } } diff --git a/test/jest/acceptance/analytics.spec.ts b/test/jest/acceptance/analytics.spec.ts index 11f0db64b2..afa0f3b659 100644 --- a/test/jest/acceptance/analytics.spec.ts +++ b/test/jest/acceptance/analytics.spec.ts @@ -48,12 +48,16 @@ describe('analytics module', () => { expect(code).toBe(0); + const requests = server.getRequests().filter((value) => { + return value.url == '/api/v1/analytics/cli'; + }); + if (isCLIV2()) { // in this case an extra analytics event is being sent, which needs to be dropped - server.popRequest(); + requests.pop(); } - const lastRequest = server.popRequest(); + const lastRequest = requests.pop(); expect(lastRequest).toMatchObject({ headers: { @@ -121,12 +125,16 @@ describe('analytics module', () => { expect(code).toBe(1); + const requests = server.getRequests().filter((value) => { + return value.url == '/api/v1/analytics/cli'; + }); + if (isCLIV2()) { // in this case an extra analytics event is being sent, which needs to be dropped - server.popRequest(); + requests.pop(); } - const lastRequest = server.popRequest(); + const lastRequest = requests.pop(); expect(lastRequest).toMatchObject({ headers: { host: 'localhost:12345', @@ -197,12 +205,16 @@ describe('analytics module', () => { expect(code).toBe(2); + const requests = server.getRequests().filter((value) => { + return value.url == '/api/v1/analytics/cli'; + }); + if (isCLIV2()) { // in this case an extra analytics event is being sent, which needs to be dropped - server.popRequest(); + requests.pop(); } - const lastRequest = server.popRequest(); + const lastRequest = requests.pop(); expect(lastRequest).toMatchObject({ headers: { host: 'localhost:12345', @@ -261,19 +273,23 @@ describe('analytics module', () => { 'npm/with-vulnerable-lodash-dep', ); - const { code } = await runSnykCLI('test', { + const { code } = await runSnykCLI('test --org=1234', { cwd: project.path(), env, }); expect(code).toBe(2); + const requests = server.getRequests().filter((value) => { + return value.url.includes('/api/v1/analytics/cli'); + }); + if (isCLIV2()) { // in this case an extra analytics event is being sent, which needs to be dropped - server.popRequest(); + requests.pop(); } - const lastRequest = server.popRequest(); + const lastRequest = requests.pop(); expect(lastRequest).toMatchObject({ query: {}, body: { @@ -335,12 +351,16 @@ describe('analytics module', () => { expect(code).toBe(0); + const requests = server.getRequests().filter((value) => { + return value.url == '/api/v1/analytics/cli'; + }); + if (isCLIV2()) { // in this case an extra analytics event is being sent, which needs to be dropped - server.popRequest(); + requests.pop(); } - const lastRequest = server.popRequest(); + const lastRequest = requests.pop(); expect(lastRequest).toMatchObject({ headers: { host: 'localhost:12345', @@ -452,7 +472,11 @@ describe('analytics module', () => { }); expect(code).toBe(0); - const lastRequest = server.popRequest(); + const requests = server.getRequests().filter((value) => { + return value.url == '/api/v1/analytics/cli'; + }); + + const lastRequest = requests.pop(); expect(lastRequest).toBeUndefined(); }); }); diff --git a/test/jest/acceptance/iac/cli-share-results.spec.ts b/test/jest/acceptance/iac/cli-share-results.spec.ts index 4205d7e927..c33cc9ecb1 100644 --- a/test/jest/acceptance/iac/cli-share-results.spec.ts +++ b/test/jest/acceptance/iac/cli-share-results.spec.ts @@ -174,7 +174,7 @@ describe('CLI Share Results', () => { server.setNextStatusCode(429); const { stdout, exitCode } = await run( - 'snyk iac test ./iac/arm/rule_test.json --report --project-business-criticality=high', + 'snyk iac test ./iac/arm/rule_test.json --report --project-business-criticality=high --org=1234', ); expect(stdout).toMatch(/test limit reached/i); diff --git a/test/jest/acceptance/snyk-fix/fix.spec.ts b/test/jest/acceptance/snyk-fix/fix.spec.ts index 2fdf728d59..7186cf75ca 100644 --- a/test/jest/acceptance/snyk-fix/fix.spec.ts +++ b/test/jest/acceptance/snyk-fix/fix.spec.ts @@ -91,7 +91,7 @@ describe('snyk fix', () => { it('fails when api requests fail', async () => { const project = await createProjectFromWorkspace('no-vulns'); server.setNextStatusCode(500); - const { code, stdout, stderr } = await runSnykCLI('fix', { + const { code, stdout, stderr } = await runSnykCLI('fix --org=1234', { cwd: project.path(), env, }); diff --git a/test/jest/acceptance/snyk-test/fail-on.spec.ts b/test/jest/acceptance/snyk-test/fail-on.spec.ts index dd88417469..f83feb256f 100644 --- a/test/jest/acceptance/snyk-test/fail-on.spec.ts +++ b/test/jest/acceptance/snyk-test/fail-on.spec.ts @@ -42,7 +42,8 @@ describe('snyk test --fail-on', () => { const project = await createProjectFromWorkspace('fail-on/' + workspace); server.setNextResponse(await project.read('vulns-result.json')); - const { code } = await runSnykCLI(`test --fail-on=${failOn}`, { + // setting the "org" is a workaround to fix this test, the limitation of a single next response is actually the root cause because it requires the first request to be the test request. + const { code } = await runSnykCLI(`test --fail-on=${failOn} --org=1234`, { cwd: project.path(), env, }); diff --git a/test/jest/acceptance/snyk-test/missing-node-modules.spec.ts b/test/jest/acceptance/snyk-test/missing-node-modules.spec.ts index 950970f197..5094a2494d 100644 --- a/test/jest/acceptance/snyk-test/missing-node-modules.spec.ts +++ b/test/jest/acceptance/snyk-test/missing-node-modules.spec.ts @@ -53,7 +53,7 @@ describe('snyk test with missing node_modules', () => { test('does not throw when missing node_modules & package.json has no dependencies', async () => { server.setNextResponse(noVulnsResult); const project = await createProject('npm/no-dependencies'); - const { code, stdout } = await runSnykCLI('test', { + const { code, stdout } = await runSnykCLI('test --org=1234', { cwd: project.path(), env, }); @@ -64,7 +64,7 @@ describe('snyk test with missing node_modules', () => { test('does not throw when missing node_modules & package.json has no dependencies (with --dev)', async () => { server.setNextResponse(noVulnsResult); const project = await createProject('npm/no-dependencies'); - const { code, stdout } = await runSnykCLI('test --dev', { + const { code, stdout } = await runSnykCLI('test --dev --org=1234', { cwd: project.path(), env, }); diff --git a/test/jest/acceptance/snyk-test/snyk-test-local-policy-file.spec.ts b/test/jest/acceptance/snyk-test/snyk-test-local-policy-file.spec.ts index b56abf3ee4..466a801005 100644 --- a/test/jest/acceptance/snyk-test/snyk-test-local-policy-file.spec.ts +++ b/test/jest/acceptance/snyk-test/snyk-test-local-policy-file.spec.ts @@ -103,7 +103,9 @@ describe('`snyk test` with `--file=`', () => { // check that we're including the policy file in the request and that the policy // includes the ignored vuln id from the .snyk file - const testDepGraphRequest = server.getRequests()[0]; + const testDepGraphRequest = server.getRequests().find((value) => { + return value.url == '/api/v1/test-dep-graph?org='; + }); expect(testDepGraphRequest.body.policy).toBeDefined(); expect(testDepGraphRequest.body.policy).toContain( 'SNYK-JS-LODASH-590103', @@ -128,7 +130,9 @@ describe('`snyk test` with `--file=`', () => { // check that we're including the policy file in the request and that the policy // includes the ignored vuln id from the .snyk file - const testDepGraphRequest = server.getRequests()[0]; + const testDepGraphRequest = server.getRequests().find((value) => { + return value.url == '/api/v1/test-dep-graph?org='; + }); expect(testDepGraphRequest.body.policy).toBeDefined(); expect(testDepGraphRequest.body.policy).toContain( 'SNYK-JS-LODASH-590103',