diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 4820c18..a8d82a9 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -13,30 +13,19 @@ jobs: - name: Checkout Source uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - - - name: Static Code Analysis - uses: golangci/golangci-lint-action@v3 - with: - args: | - --timeout 5m --out-${NO_FUTURE}format colored-line-number --enable errcheck,gosimple,govet,ineffassign,staticcheck,typecheck,unused,gocritic,asasalint,asciicheck,errchkjson,exportloopref,makezero,nilerr,unparam,unconvert,wastedassign,usestdlibvars - + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Run golangci lint + uses: jfrog/.github/actions/golangci-lint@main Go-Sec: runs-on: ubuntu-latest steps: - name: Checkout Source uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - - name: Run Gosec Security Scanner - uses: securego/gosec@master - with: - args: -exclude G204,G304,G404,G401,G505 -tests -exclude-dir \.*test\.* ./... \ No newline at end of file + - name: Run Go-Sec scanner + uses: jfrog/.github/actions/gosec-scanner@main \ No newline at end of file diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 541512e..8096e39 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -10,26 +10,10 @@ jobs: CLAssistant: runs-on: ubuntu-latest steps: - - uses: actions-ecosystem/action-regex-match@v2 - id: sign-or-recheck + - name: Run CLA Check + uses: jfrog/.github/actions/cla@main with: - text: ${{ github.event.comment.body }} - regex: '\s*(I have read the CLA Document and I hereby sign the CLA)|(recheck)\s*' - - - name: "CLA Assistant" - if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} - # Alpha Release - uses: cla-assistant/github-action@v2.3.0 - env: - # Generated and maintained by GitHub + event_comment_body: ${{ github.event.comment.body }} + event_name: ${{ github.event_name }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # JFrog's organization secret - PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_SIGN_TOKEN }} - with: - path-to-signatures: 'signed_clas.json' - path-to-document: 'https://jfrog.com/cla/' - remote-organization-name: 'jfrog' - remote-repository-name: 'jfrog-signed-clas' - # branch should not be protected - branch: 'master' - allowlist: bot* + CLA_SIGN_TOKEN: ${{ secrets.CLA_SIGN_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/frogbot-scan-pull-request.yml b/.github/workflows/frogbot-scan-pull-request.yml index 998c8c9..6479787 100644 --- a/.github/workflows/frogbot-scan-pull-request.yml +++ b/.github/workflows/frogbot-scan-pull-request.yml @@ -12,6 +12,9 @@ jobs: # "frogbot" GitHub environment can approve the pull request to be scanned. environment: frogbot steps: + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - uses: jfrog/frogbot@v2 env: JFROG_CLI_LOG_LEVEL: "DEBUG" diff --git a/.github/workflows/frogbot-scan-repository.yml b/.github/workflows/frogbot-scan-repository.yml index 2342d4d..9a230bb 100644 --- a/.github/workflows/frogbot-scan-repository.yml +++ b/.github/workflows/frogbot-scan-repository.yml @@ -16,6 +16,9 @@ jobs: # The repository scanning will be triggered periodically on the following branches. branch: [ "master" ] steps: + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - uses: jfrog/frogbot@v2 env: JFROG_CLI_LOG_LEVEL: "DEBUG" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5aa357..95f199b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,19 +17,8 @@ jobs: - name: Checkout Source uses: actions/checkout@v4 - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - name: Tests run: go test -v -race -covermode atomic -coverprofile=covprofile ./... diff --git a/crypto/checksum.go b/crypto/checksum.go index 9f08666..9bbe84b 100644 --- a/crypto/checksum.go +++ b/crypto/checksum.go @@ -27,7 +27,8 @@ const ( var algorithmFunc = map[Algorithm]func() hash.Hash{ // Go native crypto algorithms: - MD5: md5.New, + MD5: md5.New, + //#nosec G401 -- Sha1 is supported by Artifactory. SHA1: sha1.New, // sha256-simd algorithm: SHA256: sha256.New, diff --git a/fanout/reader_test.go b/fanout/reader_test.go index cbe6b25..449867e 100644 --- a/fanout/reader_test.go +++ b/fanout/reader_test.go @@ -2,6 +2,7 @@ package fanout import ( "bytes" + //#nosec G505 -- Sha1 is supported by Artifactory. "crypto/sha1" "crypto/sha256" "encoding/hex" @@ -66,6 +67,7 @@ func TestFanoutRead(t *testing.T) { } func TestFanoutProgressiveRead(t *testing.T) { + //#nosec G401 -- Sha1 is supported by Artifactory. hash1 := sha1.New() proc1 := func(p []byte) (err error) { if _, err := hash1.Write(p); err != nil { @@ -104,7 +106,7 @@ func TestFanoutProgressiveRead(t *testing.T) { func TestFanoutProgressiveReadError(t *testing.T) { const errmsg = "ERRSHA1" - + //#nosec G401 -- Sha1 is supported by Artifactory. hash1 := sha1.New() proc1 := func(p []byte) (err error) { return errors.New(errmsg) diff --git a/go.mod b/go.mod index 856afe8..3fab0a9 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/jfrog/archiver/v3 v3.6.0 github.com/minio/sha256-simd v1.0.1 github.com/pkg/errors v0.9.1 - github.com/schollz/progressbar/v3 v3.14.2 github.com/stretchr/testify v1.9.0 ) @@ -19,16 +18,13 @@ require ( github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.3 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect golang.org/x/sync v0.6.0 golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 65659ae..9926c52 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= @@ -12,7 +11,6 @@ github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F1w= github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI= -github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= @@ -21,11 +19,8 @@ github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y7 github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -34,12 +29,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks= -github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= @@ -52,11 +41,8 @@ github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1z golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/http/filestream/filestream.go b/http/filestream/filestream.go index 06c3b8b..ac9975e 100644 --- a/http/filestream/filestream.go +++ b/http/filestream/filestream.go @@ -10,7 +10,6 @@ import ( "os" ioutils "github.com/jfrog/gofrog/io" - "github.com/schollz/progressbar/v3" ) const ( @@ -69,11 +68,6 @@ type FileInfo struct { } func WriteFilesToStream(multipartWriter *multipart.Writer, filesList []*FileInfo) (err error) { - return WriteFilesToStreamWithProgressBar(multipartWriter, filesList, nil) -} - -// Deprecated: Use WriteFilesToStream instead -func WriteFilesToStreamWithProgressBar(multipartWriter *multipart.Writer, filesList []*FileInfo, bar *progressbar.ProgressBar) (err error) { // Close finishes the multipart message and writes the trailing // boundary end line to the output, thereby marking the EOF. defer ioutils.Close(multipartWriter, &err) @@ -82,12 +76,6 @@ func WriteFilesToStreamWithProgressBar(multipartWriter *multipart.Writer, filesL // Returning the error from writeFile with a possible error from the writeErr function return errors.Join(err, writeErr(multipartWriter, file, err)) } - if bar != nil { - err = bar.Add(1) - if err != nil { - return fmt.Errorf("failed to update progress bar: %w", err) - } - } } return nil diff --git a/http/retryexecutor/retryexecutor.go b/http/retryexecutor/retryexecutor.go new file mode 100644 index 0000000..d66aaae --- /dev/null +++ b/http/retryexecutor/retryexecutor.go @@ -0,0 +1,107 @@ +package retryexecutor + +import ( + "context" + "errors" + "fmt" + "github.com/jfrog/gofrog/log" + "time" +) + +type ExecutionHandlerFunc func() (shouldRetry bool, err error) + +type RetryExecutor struct { + // The context + Context context.Context + + // The amount of retries to perform. + MaxRetries int + + // Number of milliseconds to sleep between retries. + RetriesIntervalMilliSecs int + + // Message to display when retrying. + ErrorMessage string + + // Prefix to print at the beginning of each log. + LogMsgPrefix string + + // ExecutionHandler is the operation to run with retries. + ExecutionHandler ExecutionHandlerFunc +} + +func (runner *RetryExecutor) Execute() error { + var err error + var shouldRetry bool + for i := 0; i <= runner.MaxRetries; i++ { + // Run ExecutionHandler + shouldRetry, err = runner.ExecutionHandler() + + // If we should not retry, return. + if !shouldRetry { + return err + } + if cancelledErr := runner.checkCancelled(); cancelledErr != nil { + return cancelledErr + } + + // Print retry log message + runner.LogRetry(i, err) + + // Going to sleep for RetryInterval milliseconds + if runner.RetriesIntervalMilliSecs > 0 && i < runner.MaxRetries { + time.Sleep(time.Millisecond * time.Duration(runner.RetriesIntervalMilliSecs)) + } + } + // If the error is not nil, return it and log the timeout message. Otherwise, generate new error. + if err != nil { + log.Info(runner.getTimeoutErrorMsg()) + return err + } + return TimeoutError{runner.getTimeoutErrorMsg()} +} + +// Error of this type will be returned if the executor reaches timeout and no other error is returned by the execution handler. +type TimeoutError struct { + errMsg string +} + +func (retryErr TimeoutError) Error() string { + return retryErr.errMsg +} + +func (runner *RetryExecutor) getTimeoutErrorMsg() string { + prefix := "" + if runner.LogMsgPrefix != "" { + prefix = runner.LogMsgPrefix + " " + } + return fmt.Sprintf("%sexecutor timeout after %v attempts with %v milliseconds wait intervals", prefix, runner.MaxRetries, runner.RetriesIntervalMilliSecs) +} + +func (runner *RetryExecutor) LogRetry(attemptNumber int, err error) { + message := fmt.Sprintf("%s(Attempt %v)", runner.LogMsgPrefix, attemptNumber+1) + if runner.ErrorMessage != "" { + message = fmt.Sprintf("%s - %s", message, runner.ErrorMessage) + } + if err != nil { + message = fmt.Sprintf("%s: %s", message, err.Error()) + } + + if err != nil || runner.ErrorMessage != "" { + log.Warn(message) + } else { + log.Debug(message) + } +} + +func (runner *RetryExecutor) checkCancelled() error { + if runner.Context == nil { + return nil + } + contextErr := runner.Context.Err() + if errors.Is(contextErr, context.Canceled) { + log.Info("Retry executor was cancelled") + return contextErr + } + return nil +} diff --git a/http/retryexecutor/retryexecutor_test.go b/http/retryexecutor/retryexecutor_test.go new file mode 100644 index 0000000..43949f1 --- /dev/null +++ b/http/retryexecutor/retryexecutor_test.go @@ -0,0 +1,90 @@ +package retryexecutor + +import ( + "context" + "errors" + "github.com/jfrog/gofrog/log" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRetryExecutorSuccess(t *testing.T) { + retriesToPerform := 10 + breakRetriesAt := 4 + runCount := 0 + executor := RetryExecutor{ + MaxRetries: retriesToPerform, + RetriesIntervalMilliSecs: 0, + ErrorMessage: "Testing RetryExecutor", + ExecutionHandler: func() (bool, error) { + runCount++ + if runCount == breakRetriesAt { + log.Warn("Breaking after", runCount-1, "retries") + return false, nil + } + return true, nil + }, + } + + assert.NoError(t, executor.Execute()) + assert.Equal(t, breakRetriesAt, runCount) +} + +func TestRetryExecutorTimeoutWithDefaultError(t *testing.T) { + retriesToPerform := 5 + runCount := 0 + + executor := RetryExecutor{ + MaxRetries: retriesToPerform, + RetriesIntervalMilliSecs: 0, + ErrorMessage: "Testing RetryExecutor", + ExecutionHandler: func() (bool, error) { + runCount++ + return true, nil + }, + } + + assert.Equal(t, executor.Execute(), TimeoutError{executor.getTimeoutErrorMsg()}) + assert.Equal(t, retriesToPerform+1, runCount) +} + +func TestRetryExecutorTimeoutWithCustomError(t *testing.T) { + retriesToPerform := 5 + runCount := 0 + + executionHandler := errors.New("retry failed due to reason") + + executor := RetryExecutor{ + MaxRetries: retriesToPerform, + RetriesIntervalMilliSecs: 0, + ErrorMessage: "Testing RetryExecutor", + ExecutionHandler: func() (bool, error) { + runCount++ + return true, executionHandler + }, + } + + assert.Equal(t, executor.Execute(), executionHandler) + assert.Equal(t, retriesToPerform+1, runCount) +} + +func TestRetryExecutorCancel(t *testing.T) { + retriesToPerform := 5 + runCount := 0 + + retryContext, cancelFunc := context.WithCancel(context.Background()) + executor := RetryExecutor{ + Context: retryContext, + MaxRetries: retriesToPerform, + RetriesIntervalMilliSecs: 0, + ErrorMessage: "Testing RetryExecutor", + ExecutionHandler: func() (bool, error) { + runCount++ + return true, nil + }, + } + + cancelFunc() + assert.EqualError(t, executor.Execute(), context.Canceled.Error()) + assert.Equal(t, 1, runCount) +} diff --git a/io/cmd.go b/io/cmd.go index 39b227a..2f2d85f 100644 --- a/io/cmd.go +++ b/io/cmd.go @@ -243,7 +243,7 @@ func (config *Command) GetCmd() (cmd *exec.Cmd) { if config.CmdName != "" { cmdStr = append(cmdStr, config.CmdName) } - if config.CmdArgs != nil && len(config.CmdArgs) > 0 { + if len(config.CmdArgs) > 0 { cmdStr = append(cmdStr, config.CmdArgs...) } cmd = exec.Command(config.Executable, cmdStr...) diff --git a/io/fileutils.go b/io/fileutils.go index 53b4776..fba95af 100644 --- a/io/fileutils.go +++ b/io/fileutils.go @@ -2,12 +2,13 @@ package io import ( "bufio" + "crypto/rand" cr "crypto/rand" "encoding/json" "errors" "fmt" "io" - "math/rand" + "math/big" "net/http" "os" "path" @@ -35,16 +36,15 @@ type RandFile struct { const buflen = 4096 -var src = rand.NewSource(time.Now().UnixNano()) - -// #nosec G404 - No cryptographic level encryption is needed in random file -var rnd = rand.New(src) - // Create a temp file with the requested prefix at the provided dir. File length and contents are random, up to the requested max length. func CreateRandomLenFile(maxLen int, filesDir string, prefix string) string { file, _ := os.CreateTemp(filesDir, prefix) fname := file.Name() - len := rnd.Intn(maxLen) + n, err := rand.Int(rand.Reader, big.NewInt(int64(maxLen))) + if err != nil { + panic(fmt.Errorf("failed to generate random number: %w", err)) + } + len := int(n.Int64()) created, err := CreateRandFile(fname, len) if err != nil { panic(err) @@ -127,7 +127,7 @@ func walk(path string, info os.FileInfo, walkFn WalkFunc, visitedDirSymlinks map } err = walkFn(path, info, nil) if err != nil { - if info.IsDir() && err == ErrSkipDir { + if info.IsDir() && errors.Is(err, ErrSkipDir) { return nil } return err @@ -161,13 +161,13 @@ func walk(path string, info os.FileInfo, walkFn WalkFunc, visitedDirSymlinks map } fileInfo, err := fileHandler(filename) if err != nil { - if err := walkFn(filename, fileInfo, err); err != nil && err != ErrSkipDir { + if err := walkFn(filename, fileInfo, err); err != nil && !errors.Is(err, ErrSkipDir) { return err } } else { err = walk(filename, fileInfo, walkFn, visitedDirSymlinks, walkIntoDirSymlink) if err != nil { - if !fileInfo.IsDir() || err != ErrSkipDir { + if !fileInfo.IsDir() || !errors.Is(err, ErrSkipDir) { return err } } diff --git a/io/fileutils_test.go b/io/fileutils_test.go index 235288b..e627214 100644 --- a/io/fileutils_test.go +++ b/io/fileutils_test.go @@ -144,7 +144,7 @@ func TestMoveFile_NoPerm(t *testing.T) { destFileContent, err := os.ReadFile(destPath) assert.NoError(t, err) assert.Equal(t, "src", string(destFileContent)) - + // Assert source file removed assert.NoFileExists(t, sourcePath) } diff --git a/lru/lru_base.go b/lru/lru_base.go index 8522b51..737dbac 100644 --- a/lru/lru_base.go +++ b/lru/lru_base.go @@ -56,13 +56,17 @@ func (c *cacheBase) Get(key string) (value interface{}, ok bool) { if c.Expiry != time.Duration(0) { unixNow := time.Now().UnixNano() / int64(time.Millisecond) unixExpiry := int64(c.Expiry / time.Millisecond) - if (unixNow - ele.Value.(*entry).timeInsert) > unixExpiry { - c.removeElement(ele) - return nil, false + if ent, ok := ele.Value.(*entry); ok { + if (unixNow - ent.timeInsert) > unixExpiry { + c.removeElement(ele) + return nil, false + } } } c.ll.MoveToFront(ele) - return ele.Value.(*entry).value, true + if ent, ok := ele.Value.(*entry); ok { + return ent.value, true + } } return nil, false } @@ -70,8 +74,10 @@ func (c *cacheBase) Get(key string) (value interface{}, ok bool) { // Updates element's value without updating its "Least-Recently-Used" status func (c *cacheBase) UpdateElement(key string, value interface{}) { if ee, ok := c.cache[key]; ok { - ee.Value.(*entry).value = value - return + if ent, ok := ee.Value.(*entry); ok { + ent.value = value + return + } } } @@ -90,10 +96,12 @@ func (c *cacheBase) RemoveOldest() { func (c *cacheBase) removeElement(e *list.Element) { c.ll.Remove(e) - kv := e.Value.(*entry) - delete(c.cache, kv.key) - if c.OnEvicted != nil { - c.OnEvicted(kv.key, kv.value) + kv, ok := e.Value.(*entry) + if ok { + delete(c.cache, kv.key) + if c.OnEvicted != nil { + c.OnEvicted(kv.key, kv.value) + } } } @@ -105,11 +113,13 @@ func (c *cacheBase) Len() int { // Clear purges all stored items from the cache. func (c *cacheBase) Clear() { for _, e := range c.cache { - kv := e.Value.(*entry) - if c.OnEvicted != nil { - c.OnEvicted(kv.key, kv.value) + kv, ok := e.Value.(*entry) + if ok { + if c.OnEvicted != nil { + c.OnEvicted(kv.key, kv.value) + } + delete(c.cache, kv.key) } - delete(c.cache, kv.key) } c.ll.Init() } diff --git a/parallel/bounded_runner_test.go b/parallel/bounded_runner_test.go index 0126dca..0bbc5c1 100644 --- a/parallel/bounded_runner_test.go +++ b/parallel/bounded_runner_test.go @@ -1,8 +1,9 @@ package parallel import ( + "crypto/rand" "fmt" - "math/rand" + "math/big" "strconv" "strings" "sync" @@ -199,7 +200,11 @@ func produceTasks(runner *runner, results chan int, errorsQueue *ErrorsQueue, ta func createSuccessfulFlowTaskFunc(num int, result chan int) TaskFunc { return func(threadId int) error { result <- num - time.Sleep(time.Millisecond * time.Duration(rand.Intn(50))) + n, err := rand.Int(rand.Reader, big.NewInt(50)) + if err != nil { + return err + } + time.Sleep(time.Millisecond * time.Duration(n.Int64())) return nil } } @@ -210,7 +215,11 @@ func createTaskWithErrorFunc(num int, result chan int) TaskFunc { return fmt.Errorf("num: %d, above 50 going to stop", num) } result <- num - time.Sleep(time.Millisecond * time.Duration(rand.Intn(50))) + n, err := rand.Int(rand.Reader, big.NewInt(50)) + if err != nil { + return err + } + time.Sleep(time.Millisecond * time.Duration(n.Int64())) return nil } } @@ -221,7 +230,11 @@ func createTaskWithIntAsErrorFunc(num int, result chan int) TaskFunc { return fmt.Errorf("%d", num) } result <- num - time.Sleep(time.Millisecond * time.Duration(rand.Intn(50))) + n, err := rand.Int(rand.Reader, big.NewInt(50)) + if err != nil { + return err + } + time.Sleep(time.Millisecond * time.Duration(n.Int64())) return nil } } diff --git a/parallel/runner_test.go b/parallel/runner_test.go index bf2433a..4036641 100644 --- a/parallel/runner_test.go +++ b/parallel/runner_test.go @@ -1,9 +1,10 @@ package parallel import ( + "crypto/rand" "errors" "fmt" - "math/rand" + "math/big" "sync" "testing" "time" @@ -40,7 +41,9 @@ func TestAddTask(t *testing.T) { x := i _, err := runner.AddTask(func(int) error { results <- x - time.Sleep(time.Millisecond * time.Duration(rand.Intn(50))) + n, err := rand.Int(rand.Reader, big.NewInt(50)) + assert.NoError(t, err) + time.Sleep(time.Millisecond * time.Duration(n.Int64())) if float64(x) > float64(count)/2 { return fmt.Errorf("second half value %d not counted", x) }