diff --git a/cliv2/cmd/cliv2/main.go b/cliv2/cmd/cliv2/main.go index 45c35a9df7..97821485cd 100644 --- a/cliv2/cmd/cliv2/main.go +++ b/cliv2/cmd/cliv2/main.go @@ -566,7 +566,10 @@ func MainWithErrorCode() int { } // cleanup resources in use - basic_workflows.Cleanup() + _, err = globalEngine.Invoke(basic_workflows.WORKFLOWID_GLOBAL_CLEANUP) + if err != nil { + globalLogger.Printf("Failed to cleanup %v", err) + } return exitCode } diff --git a/cliv2/go.mod b/cliv2/go.mod index ab7199b371..2aa4db07a4 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -17,7 +17,7 @@ require ( github.com/snyk/cli-extension-sbom v0.0.0-20240812130014-3f4e892f15ec github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1 github.com/snyk/error-catalog-golang-public v0.0.0-20240809094525-c48d19c27edb - github.com/snyk/go-application-framework v0.0.0-20240816082313-d6797668c3be + github.com/snyk/go-application-framework v0.0.0-20240822160346-edf22a8795be github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 github.com/snyk/snyk-iac-capture v0.6.5 github.com/snyk/snyk-ls v0.0.0-20240814110458-759bec2da65d diff --git a/cliv2/go.sum b/cliv2/go.sum index 10df96b493..1614eda480 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -744,8 +744,8 @@ github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1 h1:9RKY9NdX5DrJ github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM= github.com/snyk/error-catalog-golang-public v0.0.0-20240809094525-c48d19c27edb h1:w9tJhpTFxWqAhLeraGsMExDjGK9x5Dwj1NRFwb+t+QE= github.com/snyk/error-catalog-golang-public v0.0.0-20240809094525-c48d19c27edb/go.mod h1:Ytttq7Pw4vOCu9NtRQaOeDU2dhBYUyNBe6kX4+nIIQ4= -github.com/snyk/go-application-framework v0.0.0-20240816082313-d6797668c3be h1:iEcoKC6UeEyLwvu5o2mwCGX1ExQrN+lIbb/r5UGFYdo= -github.com/snyk/go-application-framework v0.0.0-20240816082313-d6797668c3be/go.mod h1:zgYTVG71nX7zTb3ELeRlnwE/uKQxeOyQmAHtg6bC4uU= +github.com/snyk/go-application-framework v0.0.0-20240822160346-edf22a8795be h1:5m55hR2Vmvx4dqeWNOjRwppAOBvmTM/BodJCHAbFoEY= +github.com/snyk/go-application-framework v0.0.0-20240822160346-edf22a8795be/go.mod h1:zgYTVG71nX7zTb3ELeRlnwE/uKQxeOyQmAHtg6bC4uU= github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 h1:CEQuYv0Go6MEyRCD3YjLYM2u3Oxkx8GpCpFBd4rUTUk= github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65/go.mod h1:88KbbvGYlmLgee4OcQ19yr0bNpXpOr2kciOthaSzCAg= github.com/snyk/policy-engine v0.30.11 h1:wUy5LMar2vccMbNM62MSBRdjAQAhAbIm7aNXXO+g2tk= diff --git a/cliv2/internal/cliv2/cliv2.go b/cliv2/internal/cliv2/cliv2.go index aa4cd048cf..c802d1063a 100644 --- a/cliv2/internal/cliv2/cliv2.go +++ b/cliv2/internal/cliv2/cliv2.go @@ -13,12 +13,9 @@ import ( "os" "os/exec" "path" - "path/filepath" "regexp" "slices" - "strconv" "strings" - "syscall" "time" "github.com/gofrs/flock" @@ -141,11 +138,6 @@ func (c *CLI) Init() (err error) { func (c *CLI) ClearCache() error { err := c.clearVersionFolders() - if err != nil { - return err - } - - err = c.clearTemporaryProcessFolders() return err } @@ -178,58 +170,6 @@ func (c *CLI) clearVersionFolders() error { return nil } -func (c *CLI) clearTemporaryProcessFolders() error { - // clean up the tmp dir of the current version - maxConsecutiveDeletes := 5 - deleteCount := 0 - tempDir := filepath.Dir(c.GetTempDir()) - fileInfo, err := os.ReadDir(tempDir) - if err != nil { - return err - } - - // cleanup tmp files related to a non-existing process - processTempPattern := regexp.MustCompile("pid([0-9]*)") - for _, file := range fileInfo { - currentPath := path.Join(tempDir, file.Name()) - matches := processTempPattern.FindStringSubmatch(file.Name()) - if len(matches) == 2 { - processFound := true - pid, localError := strconv.Atoi(matches[1]) - if localError != nil { - continue - } - - p, localError := os.FindProcess(pid) - if localError != nil { - processFound = false - } - - if p != nil { - localError = p.Signal(syscall.Signal(0)) - if localError != nil { - processFound = false - } - } - - if !processFound { - deleteCount++ - err = os.RemoveAll(currentPath) - if err != nil { - c.DebugLogger.Println("Error deleting temporary files: ", currentPath) - } - } - } - - // Stop the loop after 5 deletions to not create too much overhead - if deleteCount == maxConsecutiveDeletes { - break - } - } - - return nil -} - func (c *CLI) AppendEnvironmentVariables(env []string) { c.env = append(c.env, env...) } diff --git a/cliv2/internal/cliv2/cliv2_test.go b/cliv2/internal/cliv2/cliv2_test.go index fd752b882e..b676a1ea2a 100644 --- a/cliv2/internal/cliv2/cliv2_test.go +++ b/cliv2/internal/cliv2/cliv2_test.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "path" - "path/filepath" "runtime" "sort" "testing" @@ -411,16 +410,11 @@ func Test_clearCache(t *testing.T) { lockfile := path.Join(cli.CacheDirectory, "v1.914.0.lock") randomFile := path.Join(versionNoV, "filename") currentVersion := cli.GetBinaryLocation() - tempDir := filepath.Dir(cli.GetTempDir()) - oldProcessTempDir := path.Join(tempDir, "pid123") - oldProcessTempDirFile := path.Join(oldProcessTempDir, "bla.txt") assert.NoError(t, os.Mkdir(versionWithV, 0755)) assert.NoError(t, os.Mkdir(versionNoV, 0755)) - assert.NoError(t, os.Mkdir(oldProcessTempDir, 0755)) assert.NoError(t, os.WriteFile(randomFile, []byte("Writing some strings"), 0666)) assert.NoError(t, os.WriteFile(lockfile, []byte("Writing some strings"), 0666)) - assert.NoError(t, os.WriteFile(oldProcessTempDirFile, []byte("Writing some strings"), 0666)) // clear cache err := cli.ClearCache() @@ -430,7 +424,6 @@ func Test_clearCache(t *testing.T) { assert.NoDirExists(t, versionWithV) assert.NoDirExists(t, versionNoV) assert.NoFileExists(t, randomFile) - assert.NoFileExists(t, oldProcessTempDirFile) // check if directories that need to exist still exist assert.FileExists(t, currentVersion) assert.FileExists(t, lockfile) diff --git a/cliv2/internal/proxy/proxy.go b/cliv2/internal/proxy/proxy.go index 9d41d8860c..7e945c20de 100644 --- a/cliv2/internal/proxy/proxy.go +++ b/cliv2/internal/proxy/proxy.go @@ -16,17 +16,17 @@ import ( "github.com/rs/zerolog" "github.com/snyk/go-application-framework/pkg/configuration" "github.com/snyk/go-application-framework/pkg/networking" + "github.com/snyk/go-application-framework/pkg/networking/certs" pkg_utils "github.com/snyk/go-application-framework/pkg/utils" - "github.com/snyk/go-application-framework/pkg/networking/certs" "github.com/snyk/go-application-framework/pkg/networking/middleware" "github.com/snyk/go-httpauth/pkg/httpauth" - "github.com/snyk/cli/cliv2/internal/constants" - "github.com/snyk/cli/cliv2/internal/utils" - "github.com/elazarl/goproxy" "github.com/elazarl/goproxy/ext/auth" + + "github.com/snyk/cli/cliv2/internal/constants" + "github.com/snyk/cli/cliv2/internal/utils" ) type WrapperProxy struct { @@ -55,17 +55,17 @@ const ( PROXY_USERNAME = "snykcli" ) -func NewWrapperProxy(config configuration.Configuration, cliVersion string, debugLogger *zerolog.Logger) (*WrapperProxy, error) { - var p WrapperProxy - p.cliVersion = cliVersion - p.addHeaderFunc = func(request *http.Request) error { return nil } +type CaData struct { + CertPool *x509.CertPool + CertFile string +} +func InitCA(config configuration.Configuration, cliVersion string, logger *zerolog.Logger) (*CaData, error) { cacheDirectory := config.GetString(configuration.CACHE_PATH) - insecureSkipVerify := config.GetBool(configuration.INSECURE_HTTPS) certName := "snyk-embedded-proxy" - p.DebugLogger = debugLogger - certPEMBlock, keyPEMBlock, err := certs.MakeSelfSignedCert(certName, []string{}, log.New(&pkg_utils.ToZeroLogDebug{Logger: p.DebugLogger}, "", 0)) + logWriter := pkg_utils.ToZeroLogDebug{Logger: logger} + certPEMBlock, keyPEMBlock, err := certs.MakeSelfSignedCert(certName, []string{}, log.New(&logWriter, "", 0)) if err != nil { return nil, err } @@ -77,12 +77,12 @@ func NewWrapperProxy(config configuration.Configuration, cliVersion string, debu } certFile, err := os.CreateTemp(tmpDirectory, "snyk-cli-cert-*.crt") if err != nil { - fmt.Println("failed to create temp cert file") + logger.Println("failed to create temp cert file") return nil, err } defer certFile.Close() - p.CertificateLocation = certFile.Name() // gives full path, not just the name + certificateLocation := certFile.Name() // gives full path, not just the name rootCAs, err := x509.SystemCertPool() if err != nil { @@ -105,27 +105,42 @@ func NewWrapperProxy(config configuration.Configuration, cliVersion string, debu } } - debugLogger.Debug().Msgf("Using additional CAs from file: %v", extraCaCertFile) + logger.Debug().Msgf("Using additional CAs from file: %v", extraCaCertFile) } } - debugLogger.Debug().Msgf("Temporary CertificateLocation: %v", p.CertificateLocation) + logger.Debug().Msgf("Temporary CertificateLocation: %v", certificateLocation) certPEMString := string(certPEMBlock) - err = utils.WriteToFile(p.CertificateLocation, certPEMString) + err = utils.WriteToFile(certificateLocation, certPEMString) if err != nil { - fmt.Println("failed to write cert to file") + logger.Print("failed to write cert to file") return nil, err } - err = setCAFromBytes(certPEMBlock, keyPEMBlock) + err = setGlobalProxyCA(certPEMBlock, keyPEMBlock) if err != nil { return nil, err } + return &CaData{ + CertPool: rootCAs, + CertFile: certificateLocation, + }, nil +} + +func NewWrapperProxy(config configuration.Configuration, cliVersion string, debugLogger *zerolog.Logger, ca CaData) (*WrapperProxy, error) { + var p WrapperProxy + p.cliVersion = cliVersion + p.addHeaderFunc = func(request *http.Request) error { return nil } + p.DebugLogger = debugLogger + p.CertificateLocation = ca.CertFile + + insecureSkipVerify := config.GetBool(configuration.INSECURE_HTTPS) + p.transport = &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: insecureSkipVerify, // goproxy defaults to true - RootCAs: rootCAs, + RootCAs: ca.CertPool, }, } @@ -242,18 +257,9 @@ func (p *WrapperProxy) Stop() { func (p *WrapperProxy) Close() { p.Stop() - - p.DebugLogger.Print("deleting temp cert file:", p.CertificateLocation) - err := os.Remove(p.CertificateLocation) - if err != nil { - p.DebugLogger.Print("failed to delete cert file") - p.DebugLogger.Print(err) - } else { - p.DebugLogger.Print("deleted temp cert file:", p.CertificateLocation) - } } -func setCAFromBytes(certPEMBlock []byte, keyPEMBlock []byte) error { +func setGlobalProxyCA(certPEMBlock []byte, keyPEMBlock []byte) error { goproxyCa, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) if err != nil { return err diff --git a/cliv2/internal/proxy/proxy_test.go b/cliv2/internal/proxy/proxy_test.go index e8b68479e7..3f070dadb4 100644 --- a/cliv2/internal/proxy/proxy_test.go +++ b/cliv2/internal/proxy/proxy_test.go @@ -17,14 +17,16 @@ import ( gafUtils "github.com/snyk/go-application-framework/pkg/utils" "github.com/snyk/go-httpauth/pkg/httpauth" + "github.com/stretchr/testify/assert" + "github.com/snyk/cli/cliv2/internal/constants" "github.com/snyk/cli/cliv2/internal/proxy" "github.com/snyk/cli/cliv2/internal/utils" - - "github.com/stretchr/testify/assert" + "github.com/snyk/cli/cliv2/pkg/basic_workflows" ) var debugLogger = zerolog.New(os.Stderr).With().Timestamp().Logger() +var caData proxy.CaData func helper_getHttpClient(gateway *proxy.WrapperProxy, useProxyAuth bool) (*http.Client, error) { rootCAs, _ := x509.SystemCertPool() @@ -67,53 +69,49 @@ func helper_getHttpClient(gateway *proxy.WrapperProxy, useProxyAuth bool) (*http return proxiedClient, nil } -func setup(t *testing.T, baseCache string, version string) { +func setup(t *testing.T, baseCache string, version string) configuration.Configuration { t.Helper() err := utils.CreateAllDirectories(baseCache, version) assert.Nil(t, err) + config := configuration.NewInMemory() + config.Set(configuration.CACHE_PATH, baseCache) + caData, err = basic_workflows.GetGlobalCertAuthority(config, &debugLogger) + assert.Nil(t, err) + return config } func teardown(t *testing.T, baseCache string) { t.Helper() err := os.RemoveAll(baseCache) + basic_workflows.CleanupGlobalCertAuthority(&debugLogger) assert.Nil(t, err) } -func Test_closingProxyDeletesTempCert(t *testing.T) { +func Test_CleanupCertFile(t *testing.T) { basecache := "testcache" version := "1.1.1" - setup(t, basecache, version) - defer teardown(t, basecache) - - config := configuration.NewInMemory() - config.Set(configuration.CACHE_PATH, basecache) - config.Set(configuration.INSECURE_HTTPS, false) + config := setup(t, basecache, version) + assert.NotNil(t, config) - wp, err := proxy.NewWrapperProxy(config, version, &debugLogger) - assert.Nil(t, err) + defer teardown(t, basecache) - err = wp.Start() - t.Log("proxy port:", wp.ProxyInfo().Port) - assert.Nil(t, err) + assert.FileExistsf(t, caData.CertFile, "CertFile exist") - wp.Close() + basic_workflows.CleanupGlobalCertAuthority(&debugLogger) - // assert cert file is deleted - _, err = os.Stat(wp.CertificateLocation) - assert.NotNil(t, err) // this means the file is gone + assert.NoFileExists(t, caData.CertFile, "CertFile does not exist anymore") } func Test_canGoThroughProxy(t *testing.T) { basecache := "testcache" version := "1.1.1" - config := configuration.NewInMemory() - config.Set(configuration.CACHE_PATH, basecache) - config.Set(configuration.INSECURE_HTTPS, false) - setup(t, basecache, version) + config := setup(t, basecache, version) defer teardown(t, basecache) - wp, err := proxy.NewWrapperProxy(config, version, &debugLogger) + config.Set(configuration.INSECURE_HTTPS, false) + + wp, err := proxy.NewWrapperProxy(config, version, &debugLogger, caData) assert.Nil(t, err) err = wp.Start() @@ -130,23 +128,18 @@ func Test_canGoThroughProxy(t *testing.T) { assert.Equal(t, 200, res.StatusCode) wp.Close() - - // assert cert file is deleted on Close - _, err = os.Stat(wp.CertificateLocation) - assert.NotNil(t, err) // this means the file is gone } func Test_proxyRejectsWithoutBasicAuthHeader(t *testing.T) { basecache := "testcache" version := "1.1.1" - config := configuration.NewInMemory() - config.Set(configuration.CACHE_PATH, basecache) - config.Set(configuration.INSECURE_HTTPS, false) - setup(t, basecache, version) + config := setup(t, basecache, version) defer teardown(t, basecache) - wp, err := proxy.NewWrapperProxy(config, version, &debugLogger) + config.Set(configuration.INSECURE_HTTPS, false) + + wp, err := proxy.NewWrapperProxy(config, version, &debugLogger, caData) assert.Nil(t, err) err = wp.Start() @@ -162,20 +155,14 @@ func Test_proxyRejectsWithoutBasicAuthHeader(t *testing.T) { assert.Contains(t, err.Error(), "Proxy Authentication Required") wp.Close() - - // assert cert file is deleted on Close - _, err = os.Stat(wp.CertificateLocation) - assert.NotNil(t, err) // this means the file is gone } func Test_SetUpstreamProxy(t *testing.T) { basecache := "testcache" version := "1.1.1" - config := configuration.NewInMemory() - config.Set(configuration.CACHE_PATH, basecache) - config.Set(configuration.INSECURE_HTTPS, false) - setup(t, basecache, version) + config := setup(t, basecache, version) + config.Set(configuration.INSECURE_HTTPS, false) defer teardown(t, basecache) var err error @@ -195,7 +182,7 @@ func Test_SetUpstreamProxy(t *testing.T) { httpauth.UnknownMechanism, } - objectUnderTest, err = proxy.NewWrapperProxy(config, version, &debugLogger) + objectUnderTest, err = proxy.NewWrapperProxy(config, version, &debugLogger, caData) assert.Nil(t, err) // running different cases @@ -227,12 +214,6 @@ func Test_SetUpstreamProxy(t *testing.T) { func Test_appendExtraCaCert(t *testing.T) { basecache := "testcache" version := "1.1.1" - config := configuration.NewInMemory() - config.Set(configuration.CACHE_PATH, basecache) - config.Set(configuration.INSECURE_HTTPS, false) - - setup(t, basecache, version) - defer teardown(t, basecache) loggerWrapper := log.New(&gafUtils.ToZeroLogDebug{Logger: &debugLogger}, "", 0) certPem, _, err := certs.MakeSelfSignedCert("mycert", []string{"dns"}, loggerWrapper) @@ -244,7 +225,11 @@ func Test_appendExtraCaCert(t *testing.T) { t.Setenv(constants.SNYK_CA_CERTIFICATE_LOCATION_ENV, file.Name()) - wp, err := proxy.NewWrapperProxy(config, version, &debugLogger) + config := setup(t, basecache, version) + config.Set(configuration.INSECURE_HTTPS, false) + defer teardown(t, basecache) + + wp, err := proxy.NewWrapperProxy(config, version, &debugLogger, caData) assert.Nil(t, err) certsPem, err := os.ReadFile(wp.CertificateLocation) @@ -261,14 +246,12 @@ func Test_appendExtraCaCert(t *testing.T) { func Test_proxyPropagatesAuthFailureHeader(t *testing.T) { basecache := "testcache" version := "1.1.1" - config := configuration.NewInMemory() - config.Set(configuration.CACHE_PATH, basecache) - config.Set(configuration.INSECURE_HTTPS, false) - setup(t, basecache, version) + config := setup(t, basecache, version) + config.Set(configuration.INSECURE_HTTPS, false) defer teardown(t, basecache) - wp, err := proxy.NewWrapperProxy(config, version, &debugLogger) + wp, err := proxy.NewWrapperProxy(config, version, &debugLogger, caData) assert.Nil(t, err) wp.SetHeaderFunction(func(r *http.Request) error { // Simulate a wrapped authentication failure, such as oauth refresh. @@ -288,8 +271,4 @@ func Test_proxyPropagatesAuthFailureHeader(t *testing.T) { assert.Equal(t, res.Header.Get("snyk-auth-failed"), "true") wp.Close() - - // assert cert file is deleted on Close - _, err = os.Stat(wp.CertificateLocation) - assert.NotNil(t, err) // this means the file is gone } diff --git a/cliv2/pkg/basic_workflows/globalresources.go b/cliv2/pkg/basic_workflows/globalresources.go new file mode 100644 index 0000000000..0a5caf6fc7 --- /dev/null +++ b/cliv2/pkg/basic_workflows/globalresources.go @@ -0,0 +1,87 @@ +package basic_workflows + +import ( + "os" + "sync" + + "github.com/rs/zerolog" + "github.com/snyk/go-application-framework/pkg/configuration" + "github.com/snyk/go-application-framework/pkg/workflow" + "github.com/spf13/pflag" + + "github.com/snyk/cli/cliv2/internal/cliv2" + "github.com/snyk/cli/cliv2/internal/proxy" + "github.com/snyk/cli/cliv2/internal/utils" +) + +var caSingleton *proxy.CaData +var caMutex sync.Mutex + +var WORKFLOWID_GLOBAL_CLEANUP workflow.Identifier = workflow.NewWorkflowIdentifier("internal.cleanup") + +func initCleanup(engine workflow.Engine) error { + entry, err := engine.Register(WORKFLOWID_GLOBAL_CLEANUP, workflow.ConfigurationOptionsFromFlagset(pflag.NewFlagSet("cleanup", pflag.ContinueOnError)), globalCleanupWorkflow) + if err != nil { + return err + } + entry.SetVisibility(false) + + return nil +} + +func globalCleanupWorkflow( + invocation workflow.InvocationContext, + _ []workflow.Data, +) (output []workflow.Data, err error) { + logger := invocation.GetEnhancedLogger() + config := invocation.GetConfiguration() + + CleanupGlobalCertAuthority(logger) + CleanupGlobalTempDirectory(config, logger) + + return output, err +} + +func CleanupGlobalCertAuthority(debugLogger *zerolog.Logger) { + caMutex.Lock() + defer caMutex.Unlock() + if caSingleton != nil { + err := os.Remove(caSingleton.CertFile) + if err != nil { + debugLogger.Print("Failed to delete temporary certificate file: ", caSingleton.CertFile) + debugLogger.Print(err) + } else { + debugLogger.Print("Deleted temporary certificate file: ", caSingleton.CertFile) + } + + caSingleton = nil + } +} + +func GetGlobalCertAuthority(config configuration.Configuration, debugLogger *zerolog.Logger) (proxy.CaData, error) { + caMutex.Lock() + defer caMutex.Unlock() + + if caSingleton == nil { + tmp, err := proxy.InitCA(config, cliv2.GetFullVersion(), debugLogger) + if err != nil { + return proxy.CaData{}, err + } + + caSingleton = tmp + } + + return *caSingleton, nil +} + +func CleanupGlobalTempDirectory(config configuration.Configuration, debugLogger *zerolog.Logger) { + tmpDirectory := utils.GetTemporaryDirectory(config.GetString(configuration.CACHE_PATH), cliv2.GetFullVersion()) + err := os.RemoveAll(tmpDirectory) + if err != nil { + debugLogger.Print("Failed to delete temporary directory: ", tmpDirectory) + debugLogger.Print(err) + return + } + + debugLogger.Print("Deleted temporary directory: ", tmpDirectory) +} diff --git a/cliv2/pkg/basic_workflows/globalresources_test.go b/cliv2/pkg/basic_workflows/globalresources_test.go new file mode 100644 index 0000000000..3b8ec6ebe7 --- /dev/null +++ b/cliv2/pkg/basic_workflows/globalresources_test.go @@ -0,0 +1,74 @@ +package basic_workflows + +import ( + "sync" + "testing" + "time" + + "github.com/snyk/go-application-framework/pkg/configuration" + "github.com/snyk/go-application-framework/pkg/workflow" + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" +) + +func Test_(t *testing.T) { + var mu sync.Mutex + caCertFile := "" + + testWorkflow := func( + invocation workflow.InvocationContext, + _ []workflow.Data, + ) (output []workflow.Data, err error) { + config := invocation.GetConfiguration() + logger := invocation.GetEnhancedLogger() + ca, err := GetGlobalCertAuthority(config, logger) + + assert.NoError(t, err) + assert.FileExists(t, ca.CertFile, "Cert file") + + mu.Lock() + caCertFile = ca.CertFile + mu.Unlock() + + return output, err + } + + config := configuration.NewInMemory() + engine := workflow.NewWorkFlowEngine(config) + testWorkflowId := workflow.NewWorkflowIdentifier("internal.test") + + _, err := engine.Register(testWorkflowId, workflow.ConfigurationOptionsFromFlagset(pflag.NewFlagSet("cleanup", pflag.ContinueOnError)), testWorkflow) + assert.NoError(t, err) + + err = initCleanup(engine) + assert.NoError(t, err) + + err = engine.Init() + assert.NoError(t, err) + + count := 10 + stop := make(chan int, count) + for i := range count { + go func() { + _, localErr := engine.Invoke(testWorkflowId) + assert.NoError(t, localErr) + stop <- i + }() + } + + for _ = range count { + select { + case <-stop: + case <-time.After(time.Second): + assert.Fail(t, "timeout") + return + } + } + + assert.FileExists(t, caCertFile, "Cert file") + + _, err = engine.Invoke(WORKFLOWID_GLOBAL_CLEANUP) + assert.NoError(t, err) + + assert.NoFileExists(t, caCertFile, "Cert file") +} diff --git a/cliv2/pkg/basic_workflows/init.go b/cliv2/pkg/basic_workflows/init.go new file mode 100644 index 0000000000..a2561f4e01 --- /dev/null +++ b/cliv2/pkg/basic_workflows/init.go @@ -0,0 +1,19 @@ +package basic_workflows + +import ( + "github.com/snyk/go-application-framework/pkg/workflow" +) + +func Init(engine workflow.Engine) error { + err := initLegacycli(engine) + if err != nil { + return err + } + + err = initCleanup(engine) + if err != nil { + return err + } + + return nil +} diff --git a/cliv2/pkg/basic_workflows/legacycli.go b/cliv2/pkg/basic_workflows/legacycli.go index dc3b71a440..5b4f27a685 100644 --- a/cliv2/pkg/basic_workflows/legacycli.go +++ b/cliv2/pkg/basic_workflows/legacycli.go @@ -5,13 +5,13 @@ import ( "bytes" "net/http" "os" - "sync" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/snyk/go-application-framework/pkg/auth" "github.com/snyk/go-application-framework/pkg/configuration" "github.com/snyk/go-application-framework/pkg/logging" + "github.com/snyk/go-application-framework/pkg/networking" pkg_utils "github.com/snyk/go-application-framework/pkg/utils" "github.com/snyk/go-application-framework/pkg/workflow" "github.com/snyk/go-httpauth/pkg/httpauth" @@ -25,22 +25,23 @@ import ( var WORKFLOWID_LEGACY_CLI workflow.Identifier = workflow.NewWorkflowIdentifier("legacycli") var DATATYPEID_LEGACY_CLI_STDOUT workflow.Identifier = workflow.NewTypeIdentifier(WORKFLOWID_LEGACY_CLI, "stdout") -var proxySingleton *proxy.WrapperProxy -var proxyMutex sync.Mutex - const ( PROXY_NOAUTH string = "proxy-noauth" ) -func Init(engine workflow.Engine) error { +func initLegacycli(engine workflow.Engine) error { flagset := pflag.NewFlagSet("legacycli", pflag.ContinueOnError) flagset.StringSlice(configuration.RAW_CMD_ARGS, os.Args[1:], "Command line arguments for the legacy CLI.") flagset.Bool(configuration.WORKFLOW_USE_STDIO, false, "Use StdIn and StdOut") flagset.String(configuration.WORKING_DIRECTORY, "", "CLI working directory") config := workflow.ConfigurationOptionsFromFlagset(flagset) - entry, _ := engine.Register(WORKFLOWID_LEGACY_CLI, config, legacycliWorkflow) + entry, err := engine.Register(WORKFLOWID_LEGACY_CLI, config, legacycliWorkflow) + if err != nil { + return err + } entry.SetVisibility(false) + return nil } @@ -137,24 +138,11 @@ func legacycliWorkflow( cli.SetIoStreams(os.Stdin, os.Stdout, scrubbedStderr) } - wrapperProxy, err := getProxyInstance(config, debugLogger) + wrapperProxy, err := createInternalProxy(config, debugLogger, proxyAuthenticationMechanism, networkAccess) if err != nil { return output, err } - wrapperProxy.SetUpstreamProxyAuthentication(proxyAuthenticationMechanism) - - proxyHeaderFunc := func(req *http.Request) error { - headersErr := networkAccess.AddHeaders(req) - return headersErr - } - wrapperProxy.SetHeaderFunction(proxyHeaderFunc) - - err = wrapperProxy.Start() - if err != nil { - return output, errors.Wrap(err, "Failed to start the proxy!") - } - // run the cli proxyInfo := wrapperProxy.ProxyInfo() err = cli.Execute(proxyInfo, finalizeArguments(args, config.GetStringSlice(configuration.UNKNOWN_ARGS))) @@ -179,27 +167,29 @@ func legacycliWorkflow( return output, err } -func Cleanup() { - proxyMutex.Lock() - defer proxyMutex.Unlock() - if proxySingleton != nil { - proxySingleton.Close() - proxySingleton = nil +func createInternalProxy(config configuration.Configuration, debugLogger *zerolog.Logger, proxyAuthenticationMechanism httpauth.AuthenticationMechanism, networkAccess networking.NetworkAccess) (*proxy.WrapperProxy, error) { + caData, err := GetGlobalCertAuthority(config, debugLogger) + if err != nil { + return nil, err + } + + wrapperProxy, err := proxy.NewWrapperProxy(config, cliv2.GetFullVersion(), debugLogger, caData) + if err != nil { + return nil, errors.Wrap(err, "Failed to create proxy!") } -} -func getProxyInstance(config configuration.Configuration, debugLogger *zerolog.Logger) (*proxy.WrapperProxy, error) { - var err error - proxyMutex.Lock() - defer proxyMutex.Unlock() + wrapperProxy.SetUpstreamProxyAuthentication(proxyAuthenticationMechanism) - if proxySingleton == nil { - // init proxy object - proxySingleton, err = proxy.NewWrapperProxy(config, cliv2.GetFullVersion(), debugLogger) - if err != nil { - return nil, errors.Wrap(err, "Failed to create proxy!") - } + proxyHeaderFunc := func(req *http.Request) error { + headersErr := networkAccess.AddHeaders(req) + return headersErr + } + wrapperProxy.SetHeaderFunction(proxyHeaderFunc) + + err = wrapperProxy.Start() + if err != nil { + return nil, errors.Wrap(err, "Failed to start the proxy!") } - return proxySingleton, nil + return wrapperProxy, nil } diff --git a/test/jest/acceptance/language-server-extension.spec.ts b/test/jest/acceptance/language-server-extension.spec.ts index 9040d3ee3e..0dec78998b 100644 --- a/test/jest/acceptance/language-server-extension.spec.ts +++ b/test/jest/acceptance/language-server-extension.spec.ts @@ -107,5 +107,6 @@ describe('Language Server Extension', () => { } cli.kill(9); + expect(diagnosticCount).toBeGreaterThan(0); }); }); diff --git a/test/jest/acceptance/parallel-execution.spec.ts b/test/jest/acceptance/parallel-execution.spec.ts index 79509c78d7..3d8db14046 100644 --- a/test/jest/acceptance/parallel-execution.spec.ts +++ b/test/jest/acceptance/parallel-execution.spec.ts @@ -1,20 +1,23 @@ import { runSnykCLI } from '../util/runSnykCLI'; import { RunCommandResult } from '../util/runCommand'; +import { createProject } from '../util/createProject'; jest.setTimeout(1000 * 120); describe('Parallel CLI execution', () => { - it('parallel woof', async () => { + it('parallel test', async () => { const numberOfParallelExecutions = 10; + const project = await createProject('npm/with-vulnerable-lodash-dep'); + const singleTestResult: Promise[] = []; for (let i = 0; i < numberOfParallelExecutions; i++) { - singleTestResult.push(runSnykCLI(`woof -d`)); + singleTestResult.push(runSnykCLI(`test -d`, { cwd: project.path() })); } for (let i = 0; i < numberOfParallelExecutions; i++) { const { code } = await singleTestResult[i]; - expect(code).toBe(0); + expect(code).toBe(1); } }); });