diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 5b304942..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,102 +0,0 @@ -version: 2.1 - -experimental: - notify: - branches: - only: - - main - -defaults: - environment: &environment - CIRCLE_TEST_REPORTS: /tmp/circle-reports - CIRCLE_ARTIFACTS: /tmp/circle-artifacts - - build_steps: &build_steps - steps: - - checkout - - run: - name: install pre dependencies - command: | - sudo apt-get install -yqq git - - run: go install github.com/jstemmer/go-junit-report@v1.0.0 - - run: go install github.com/kyoh86/richgo@v0.3.10 - - run: sudo apt-get update - - run: sudo apt-get install python3-pip - - run: sudo pip install pre-commit - - run: pre-commit install - - run: pre-commit run -a golangci-lint - - run: - name: Set up Code Climate test-reporter - command: | - curl -sS -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - - run: - name: Run tests - command: | - mkdir -p $CIRCLE_TEST_REPORTS - mkdir -p $CIRCLE_ARTIFACTS - trap "go-junit-report < $CIRCLE_ARTIFACTS/report.txt > $CIRCLE_TEST_REPORTS/junit.xml" EXIT - if [ -z "$DISABLE_COVERAGE" ]; then - go_cover_args="-covermode=atomic -coverpkg=./... -coverprofile /tmp/circle-artifacts/coverage.txt" - fi - go test -race $go_cover_args -v $(go list ./... | grep -v /vendor/) | tee >(richgo testfilter) > $CIRCLE_ARTIFACTS/report.txt - if [[ -z "$DISABLE_COVERAGE" && -n "$CC_TEST_REPORTER_ID" ]]; then - ./cc-test-reporter format-coverage $CIRCLE_ARTIFACTS/coverage.txt -t gocov --output $CIRCLE_ARTIFACTS/coverage.json - ./cc-test-reporter upload-coverage --input $CIRCLE_ARTIFACTS/coverage.json - fi - - run: - name: Generate coverage report - command: | - if [ -z "$DISABLE_COVERAGE" ]; then - go tool cover -html=$CIRCLE_ARTIFACTS/coverage.txt -o $CIRCLE_ARTIFACTS/coverage.html - fi - when: always - - store_test_results: - path: /tmp/circle-reports - - store_artifacts: - path: /tmp/circle-artifacts - -jobs: - go-test: - docker: - - image: cimg/go:1.22 - environment: - <<: *environment - - <<: *build_steps - - test-publish: - docker: - - image: cimg/go:1.22 - steps: - - checkout - - setup_remote_docker - - run: sudo apt-get update - - run: sudo apt-get install rpm - - run: make test-publish - github-actions-docs: - docker: - - image: cimg/node:18.8.0 - steps: - - checkout - - run: sudo apt-get update - - run: sudo apt-get install python3-pip - - run: sudo pip install pre-commit - - run: pre-commit install - - run: npm install action-docs - - run: pre-commit run -a github-action-docs - -workflows: - test: - jobs: - - go-test: - filters: - tags: - only: /.*/ - - test-publish: - requires: - - go-test - - lint: - jobs: - - github-actions-docs diff --git a/.github/actions/publish-junit/action.yml b/.github/actions/publish-junit/action.yml new file mode 100644 index 00000000..7758e18e --- /dev/null +++ b/.github/actions/publish-junit/action.yml @@ -0,0 +1,35 @@ +name: Publish JUnit Tests +description: Publishes JUnit tests to one or more sources +inputs: + files: + required: true + description: The JUnit files to upload + name: + required: true + description: The name of the suite + datadog: + required: false + description: Upload to Datadog + default: 'true' + github: + required: false + description: Upload to GitHub + default: 'true' + +runs: + using: composite + steps: + - name: Report Tests to Datadog + shell: bash + if: ${{ inputs.datadog }} == 'true' + run: datadog-ci junit upload --service ${{ inputs.name }} ${{ inputs.files }} + + - name: Test Publish + uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e # v15 + if: ${{ inputs.github }} == 'true' + with: + name: ${{ inputs.name }} + output-to: step-summary + path: ${{ inputs.files }} + reporter: java-junit + fail-on-error: 'false' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..6986e75f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,68 @@ +name: Test and Generate Docs + +on: pull_request + +jobs: + go-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + + - name: Set up Go + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed + with: + go-version-file: go.mod + + - name: Install datadog-ci + run: npm install -g @datadog/datadog-ci + + - name: Install Dependencies + run: | + go install github.com/jstemmer/go-junit-report@v1.0.0 + go install github.com/kyoh86/richgo@v0.3.10 + go install gotest.tools/gotestsum@latest + + - name: Install golangci-lint + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 + with: + version: v1.62.2 + + - name: Run tests with gotestsum + run: | + mkdir -p ${{ github.workspace }}/artifacts + mkdir -p ${{ github.workspace }}/reports + gotestsum --packages="./..." \ + --junitfile ${{ github.workspace }}/reports/go-test_go_tests.xml \ + --jsonfile ${{ github.workspace }}/artifacts/go-test_go_tests.json \ + --rerun-fails=2 \ + --rerun-fails-max-failures=10 \ + --rerun-fails-report ${{ github.workspace }}/artifacts/rerun_tests_go_tests.txt + + - name: Publish JUnit Tests + uses: ./.github/actions/publish-junit + env: + DD_API_KEY: ${{ secrets.DATADOG_API_KEY }} + with: + files: ${{ github.workspace }}/reports/go-test_go_tests.xml + name: find-code-references-in-pull-request + datadog: 'true' + github: 'true' + + github-actions-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + + - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + node-version: 20 + + - name: Install action-docs + run: npm install action-docs + + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + with: + extra_args: github-action-docs --all-files diff --git a/build/package/github-actions/github-actions.go b/build/package/github-actions/github-actions.go index 4391437c..a9619175 100644 --- a/build/package/github-actions/github-actions.go +++ b/build/package/github-actions/github-actions.go @@ -14,6 +14,10 @@ import ( o "github.com/launchdarkly/ld-find-code-refs/v2/options" ) +const ( + millisecondsInSecond = 1000 // Descriptive constant for milliseconds conversion +) + func main() { log.Init(false) dir := os.Getenv("GITHUB_WORKSPACE") @@ -55,7 +59,7 @@ func mergeGithubOptions(opts o.Options) (o.Options, error) { if event != nil { repoUrl = event.Repo.Url defaultBranch = event.Repo.DefaultBranch - updateSequenceId = int(time.Now().Unix() * 1000) // seconds to ms + updateSequenceId = int(time.Now().Unix() * millisecondsInSecond) // seconds to ms } opts.RepoType = "github" diff --git a/internal/git/git.go b/internal/git/git.go index 85fb932f..db31617a 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -21,6 +21,11 @@ import ( "github.com/launchdarkly/ld-find-code-refs/v2/internal/log" ) +const ( + // ... other constants ... + millisecondsInSecond = 1000 // Descriptive constant for milliseconds conversion +) + type Client struct { workspace string GitBranch string @@ -227,7 +232,7 @@ type CommitData struct { } // FindExtinctions searches commit history for flags that had references removed recently -func (c Client) FindExtinctions(project options.Project, flags []string, matcher search.Matcher, lookback int) ([]ld.ExtinctionRep, error) { +func (c *Client) FindExtinctions(project options.Project, flags []string, matcher search.Matcher, lookback int) ([]ld.ExtinctionRep, error) { commits, err := getCommits(c.workspace, lookback) if err != nil { return nil, err @@ -327,7 +332,7 @@ func makeExtinctionRepFromCommit(projectKey, flagKey string, commit *object.Comm return ld.ExtinctionRep{ Revision: commit.Hash.String(), Message: commit.Message, - Time: commit.Author.When.Unix() * 1000, + Time: commit.Author.When.Unix() * millisecondsInSecond, ProjKey: projectKey, FlagKey: flagKey, } @@ -342,9 +347,8 @@ func getCommits(workspace string, lookback int) ([]CommitData, error) { if err != nil { return nil, err } - commits := []CommitData{} - for i := 0; i < lookback; i++ { + for range make([]struct{}, lookback) { commit, err := logResult.Next() if err != nil { // reached end of commit tree diff --git a/internal/ld/ld.go b/internal/ld/ld.go index 13a98de0..89deddc3 100644 --- a/internal/ld/ld.go +++ b/internal/ld/ld.go @@ -44,6 +44,7 @@ const ( apiVersionHeader = "LD-API-Version" v2ApiPath = "/api/v2" reposPath = "/code-refs/repositories" + shortShaLength = 7 // Descriptive constant for SHA length ) type ConfigurationError struct { @@ -76,8 +77,8 @@ func IsTransient(err error) bool { // Fallback to default backoff if header can't be parsed // https://apidocs.launchdarkly.com/#section/Overview/Rate-limiting // Method is curried in order to avoid stubbing the time package and fallback Backoff in unit tests -func RateLimitBackoff(now func() time.Time, fallbackBackoff h.Backoff) func(time.Duration, time.Duration, int, *http.Response) time.Duration { - return func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration { +func RateLimitBackoff(now func() time.Time, fallbackBackoff h.Backoff) func(minDuration, _ time.Duration, attemptNum int, resp *http.Response) time.Duration { + return func(minDuration, max2 time.Duration, attemptNum int, resp *http.Response) time.Duration { if resp != nil { if resp.StatusCode == http.StatusTooManyRequests { if s, ok := resp.Header["X-Ratelimit-Reset"]; ok { @@ -95,7 +96,7 @@ func RateLimitBackoff(now func() time.Time, fallbackBackoff h.Backoff) func(time } } - return fallbackBackoff(min, max, attemptNum, resp) + return fallbackBackoff(minDuration, max2, attemptNum, resp) } } @@ -108,7 +109,7 @@ func InitApiClient(options ApiOptions) ApiClient { if options.RetryMax != nil && *options.RetryMax >= 0 { client.RetryMax = *options.RetryMax } - client.Backoff = RateLimitBackoff(time.Now, h.LinearJitterBackoff) + client.Backoff = RateLimitBackoff(time.Now, h.LinearJitterBackoff) //nolint:bodyclose return ApiClient{ httpClient: client, @@ -192,7 +193,7 @@ func (c ApiClient) getProjectEnvironment(projKey string) (*ldapi.Environment, er } func (c ApiClient) getFlags(projKey string, params url.Values) ([]ldapi.FeatureFlag, error) { - url := c.getPath(fmt.Sprintf("/flags/%s", projKey)) + url := c.getPath(fmt.Sprintf("/flags/%s", projKey)) //nolint:perfsprint req, err := h.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err @@ -441,7 +442,7 @@ type ldErrorResponse struct { func (c ApiClient) do(req *h.Request) (*http.Response, error) { req.Header.Set("Authorization", c.Options.ApiKey) - req.Header.Set(apiVersionHeader, apiVersion) + req.Header.Set(apiVersionHeader, apiVersion) //nolint:canonicalheader req.Header.Set("User-Agent", c.Options.UserAgent) req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Length", strconv.FormatInt(req.ContentLength, 10)) @@ -550,8 +551,8 @@ func (b BranchRep) TotalHunkCount() int { func (b BranchRep) WriteToCSV(outDir, projKey, repo, sha string) (path string, err error) { // Try to create a filename with a shortened sha, but if the sha is too short for some unexpected reason, use the branch name instead var tag string - if len(sha) >= 7 { - tag = sha[:7] + if len(sha) >= shortShaLength { + tag = sha[:shortShaLength] } else { tag = b.Name } @@ -577,7 +578,7 @@ func (b BranchRep) WriteToCSV(outDir, projKey, repo, sha string) (path string, e // sort csv by flag key sort.Slice(records, func(i, j int) bool { // sort by flagKey -> path -> startingLineNumber - for k := 0; k < 3; k++ { + for k := range [3]int{} { if records[i][k] != records[j][k] { return records[i][k] < records[j][k] } diff --git a/options/flags.go b/options/flags.go index c3d1a1be..f11a98f8 100644 --- a/options/flags.go +++ b/options/flags.go @@ -45,7 +45,7 @@ Allowed template variables: 'branchName', 'sha'. If "commitUrlTemplate" is not p { name: "contextLines", short: "C", - defaultValue: 2, + defaultValue: 2, //nolint:mnd usage: `The number of context lines to send to LaunchDarkly. If < 0, no source code will be sent to LaunchDarkly. If 0, only the lines containing flag references will be sent. If > 0, will send that number of context @@ -94,7 +94,7 @@ LaunchDarkly API is unreachable or returns an unexpected response.`, { name: "lookback", short: "l", - defaultValue: 10, + defaultValue: 10, //nolint:mnd usage: `Sets the number of git commits to search in history for whether a feature flag was removed from code. May be set to 0 to disabled this feature. Setting this option to a high value will increase search time.`, }, diff --git a/options/options.go b/options/options.go index cad15126..fbea20b8 100644 --- a/options/options.go +++ b/options/options.go @@ -195,7 +195,7 @@ func (o Options) ValidateRequired() error { } if len(o.ProjKey) > 0 && len(o.Projects) > 0 { - return fmt.Errorf("`--projKey` cannot be combined with `projects` in configuration") + return errors.New("`--projKey` cannot be combined with `projects` in configuration") } if len(o.ProjKey) > maxProjKeyLength { @@ -263,7 +263,7 @@ func (o Options) Validate() error { } if o.Revision != "" && o.Branch == "" { - return fmt.Errorf(`"branch" option is required when "revision" option is set`) + return errors.New(`"branch" option is required when "revision" option is set`) } if len(o.Projects) > 0 { diff --git a/search/matcher.go b/search/matcher.go index 765fb13a..76b0a9f0 100644 --- a/search/matcher.go +++ b/search/matcher.go @@ -83,7 +83,7 @@ func buildElementPatterns(flags []string, delimiters string) map[string][]string for _, left := range delimiters { for _, right := range delimiters { var sb strings.Builder - sb.Grow(len(flag) + 2) + sb.Grow(len(flag) + 2) //nolint:mnd sb.WriteRune(left) sb.WriteString(flag) sb.WriteRune(right)