Skip to content

Commit a0308f1

Browse files
authored
Feat/snyk timeout secs (#4942)
* feat: ci mode differentiating permanent errors Temporary failures might be treated differently from permanent failures in a CI context. A temporary network partition might be retried with an exponential backoff delay in a CI script. Such a temporary failure might even be skipped in the CI workflow, with assurance that security testing will be covered by Snyk monitors and reporting. This option gives more flexibility and self-determination in choosing security posture. In some cases landing a critical fix is more important than waiting for a network partition or temporary outage to resolve. This CI mode may be opted into by setting an environment variable SNYK_CI=1 when running the CLI. CI mode causes the following errors to terminate with a different exit code: - Authorization errors, including entitlement errors (HTTP 401 and 403), will exit code 77 (EX_NOPERM) - Other non-recoverable errors will exit code 69 (EX_UNAVAILABLE). This includes all other HTTP 4xx errors from Snyk APIs. Recoverable errors (network connection errors, HTTP 5xx) will exit code 2. The difference between CI and non-CI mode, is that the above errors will normally exit code 2. * feat: snyk_timeout_secs option Terminate CLI with exit code EX_UNAVAILABLE after a timeout, when set using SNYK_TIMEOUT_SECS=N environment variable, where N is the number of seconds the user is willing to wait for the command to complete.
1 parent 91ba735 commit a0308f1

File tree

7 files changed

+57
-7
lines changed

7 files changed

+57
-7
lines changed

cliv2/cmd/cliv2/main.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,26 @@ import (
1616
"github.com/snyk/cli-extension-dep-graph/pkg/depgraph"
1717
"github.com/snyk/cli-extension-iac-rules/iacrules"
1818
"github.com/snyk/cli-extension-sbom/pkg/sbom"
19+
"github.com/snyk/cli/cliv2/internal/cliv2"
20+
"github.com/snyk/cli/cliv2/internal/constants"
21+
"github.com/snyk/cli/cliv2/pkg/basic_workflows"
1922
"github.com/snyk/container-cli/pkg/container"
2023
"github.com/snyk/go-application-framework/pkg/analytics"
2124
"github.com/snyk/go-application-framework/pkg/app"
2225
"github.com/snyk/go-application-framework/pkg/auth"
2326
"github.com/snyk/go-application-framework/pkg/configuration"
27+
2428
localworkflows "github.com/snyk/go-application-framework/pkg/local_workflows"
2529
"github.com/snyk/go-application-framework/pkg/networking"
30+
"github.com/snyk/go-application-framework/pkg/runtimeinfo"
2631
"github.com/snyk/go-application-framework/pkg/utils"
2732
"github.com/snyk/go-application-framework/pkg/workflow"
2833
"github.com/snyk/go-httpauth/pkg/httpauth"
2934
"github.com/snyk/snyk-iac-capture/pkg/capture"
35+
3036
snykls "github.com/snyk/snyk-ls/ls_extension"
3137
"github.com/spf13/cobra"
3238
"github.com/spf13/pflag"
33-
34-
"github.com/snyk/go-application-framework/pkg/runtimeinfo"
35-
36-
"github.com/snyk/cli/cliv2/internal/cliv2"
37-
"github.com/snyk/cli/cliv2/internal/constants"
38-
"github.com/snyk/cli/cliv2/pkg/basic_workflows"
3939
)
4040

4141
var internalOS string
@@ -444,6 +444,10 @@ func MainWithErrorCode() int {
444444
defer sendAnalytics(cliAnalytics, debugLogger)
445445
}
446446

447+
setTimeout(globalConfiguration, func() {
448+
os.Exit(constants.SNYK_EXIT_CODE_EX_UNAVAILABLE)
449+
})
450+
447451
// run the extensible cli
448452
err = rootCommand.Execute()
449453

@@ -467,3 +471,16 @@ func MainWithErrorCode() int {
467471

468472
return exitCode
469473
}
474+
475+
func setTimeout(config configuration.Configuration, onTimeout func()) {
476+
timeout := config.GetInt(configuration.TIMEOUT)
477+
if timeout == 0 {
478+
return
479+
}
480+
debugLogger.Printf("Command timeout set for %d seconds", timeout)
481+
go func() {
482+
<-time.After(time.Duration(timeout) * time.Second)
483+
fmt.Fprintf(os.Stderr, "command timed out\n")
484+
onTimeout()
485+
}()
486+
}

cliv2/cmd/cliv2/main_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"os"
55
"testing"
6+
"time"
67

78
"github.com/snyk/go-application-framework/pkg/configuration"
89
localworkflows "github.com/snyk/go-application-framework/pkg/local_workflows"
@@ -213,3 +214,19 @@ func Test_runMainWorkflow_unknownargs(t *testing.T) {
213214
})
214215
}
215216
}
217+
218+
func Test_setTimeout(t *testing.T) {
219+
exitedCh := make(chan struct{})
220+
fakeExit := func() {
221+
close(exitedCh)
222+
}
223+
config := configuration.New()
224+
config.Set(configuration.TIMEOUT, 1)
225+
setTimeout(config, fakeExit)
226+
select {
227+
case <-exitedCh:
228+
break
229+
case <-time.After(5 * time.Second):
230+
t.Fatal("timeout func never executed")
231+
}
232+
}

cliv2/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce
1414
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a
1515
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f
16-
github.com/snyk/go-application-framework v0.0.0-20231121110922-9719383f0706
16+
github.com/snyk/go-application-framework v0.0.0-20231122083330-bbb0d2002b01
1717
github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530
1818
github.com/snyk/snyk-iac-capture v0.6.5
1919
github.com/snyk/snyk-ls v0.0.0-20231124091213-5a223c21e0aa

cliv2/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
255255
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
256256
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
257257
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
258+
github.com/cmars/go-application-framework v0.0.0-20231121235901-2a517c3dca80 h1:/ih3AkS+EPO51JoSgJCbS5D+5ErEEYQ5Kv3UDtBOhKU=
259+
github.com/cmars/go-application-framework v0.0.0-20231121235901-2a517c3dca80/go.mod h1:Yz/qxFyfhf0xbA+z8Vzr5IM9IDG+BS+2PiGaP1yAsEw=
258260
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
259261
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
260262
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -667,6 +669,8 @@ github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f h1:ghajT5PEiLP8
667669
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM=
668670
github.com/snyk/go-application-framework v0.0.0-20231121110922-9719383f0706 h1:z/g5P0kS7bedN07rNChlPEifKvAe9+hufGEEifPNcJg=
669671
github.com/snyk/go-application-framework v0.0.0-20231121110922-9719383f0706/go.mod h1:Yz/qxFyfhf0xbA+z8Vzr5IM9IDG+BS+2PiGaP1yAsEw=
672+
github.com/snyk/go-application-framework v0.0.0-20231122083330-bbb0d2002b01 h1:2WL20Lgh2YSifXNJ4zw3tZqX2Qa4CqM2C2m0+oWtKGw=
673+
github.com/snyk/go-application-framework v0.0.0-20231122083330-bbb0d2002b01/go.mod h1:Yz/qxFyfhf0xbA+z8Vzr5IM9IDG+BS+2PiGaP1yAsEw=
670674
github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530 h1:s9PHNkL6ueYRiAKNfd8OVxlUOqU3qY0VDbgCD1f6WQY=
671675
github.com/snyk/go-httpauth v0.0.0-20231117135515-eb445fea7530/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg=
672676
github.com/snyk/policy-engine v0.22.0 h1:od9pduGrXyfWO791X+8M1qmnvWUxaIXh0gBzGKqeseA=

cliv2/internal/constants/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package constants
22

33
const SNYK_EXIT_CODE_OK = 0
44
const SNYK_EXIT_CODE_ERROR = 2
5+
const SNYK_EXIT_CODE_EX_UNAVAILABLE = 69
56
const SNYK_INTEGRATION_NAME = "CLI_V1_PLUGIN"
67
const SNYK_INTEGRATION_NAME_ENV = "SNYK_INTEGRATION_NAME"
78
const SNYK_INTEGRATION_VERSION_ENV = "SNYK_INTEGRATION_VERSION"

src/cli/exit-codes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ export const EXIT_CODES = {
22
VULNS_FOUND: 1,
33
ERROR: 2,
44
NO_SUPPORTED_PROJECTS_DETECTED: 3,
5+
EX_UNAVAILABLE: 69,
6+
EX_NOPERM: 77,
57
};

src/cli/main.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ async function handleError(args, error) {
9696
let command = 'bad-command';
9797
let exitCode = EXIT_CODES.ERROR;
9898

99+
// If Snyk CLI is running in CI mode (SNYK_CI=1), differentiate authorization
100+
if (process.env.SNYK_CI === '1') {
101+
if (error.code === 401 || error.code === 403) {
102+
exitCode = EXIT_CODES.EX_NOPERM;
103+
} else if (error.code >= 400 && error.code < 500) {
104+
exitCode = EXIT_CODES.EX_UNAVAILABLE;
105+
}
106+
}
107+
99108
const vulnsFound = error.code === 'VULNS';
100109

101110
if (args.command === 'test' && args.options?.unmanaged) {

0 commit comments

Comments
 (0)