Compare commits

..

14 Commits

Author SHA1 Message Date
e4cc61e5b1 Merge pull request #13 from golangci/feature/action-config
use action config, don't require github token
2020-05-09 16:36:00 +03:00
27e14e0f3f use action config, don't require github token
fixes #11
2020-05-09 16:34:52 +03:00
6993abb7cd docs: use @v0.1.7 in README as the latest release in marketplace 2020-05-07 16:01:25 +03:00
ca150a071d docs: fix version, @v0 doesn't work 2020-05-07 15:59:32 +03:00
1ef31b642a Merge pull request #10 from golangci/feature/fix-github-token
docs: recommend using GITHUB_TOKEN
2020-05-07 15:44:41 +03:00
fc9d1728df docs: recommend using GITHUB_TOKEN
Fixes: #9
2020-05-07 15:42:54 +03:00
1ad27ad153 s/@v1/@v0 2020-05-07 11:26:37 +03:00
1778ebaefa dev: shorten description 2020-05-05 18:17:02 +03:00
1dd9e0522b dev: improve description 2020-05-05 18:11:57 +03:00
66883b5fcf dev: change icon and color on GitHub marketplace 2020-05-05 17:57:46 +03:00
8ea3043ee4 docs: improve info about performance 2020-05-05 17:51:32 +03:00
95f6eefffa docs: add perf stat into README.md 2020-05-05 17:36:44 +03:00
977a01f96c Merge pull request #8 from golangci/fix-go.mod-hash
fix go.mod hashsum
2020-05-05 17:20:28 +03:00
13e2c1f984 fix go.mod hashsum 2020-05-05 17:17:46 +03:00
11 changed files with 172 additions and 48206 deletions

View File

@ -15,7 +15,7 @@ jobs:
npm install npm install
npm run prepare-deps npm run prepare-deps
npm run all npm run all
git diff && git diff --cached git diff --exit-code
test: # make sure the action works on a clean machine without building test: # make sure the action works on a clean machine without building
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -23,5 +23,4 @@ jobs:
- uses: ./ - uses: ./
with: with:
version: v1.26 version: v1.26
args: --issues-exit-code=0 ./sample/... args: --issues-exit-code=0 ./sample/...
github-token: ${{ secrets.GOLANGCI_LINT_GITHUB_TOKEN }}

View File

@ -2,15 +2,14 @@
[![Build Status](https://github.com/golangci/golangci-lint-action/workflows/build-and-test/badge.svg)](https://github.com/golangci/golangci-lint-action/actions) [![Build Status](https://github.com/golangci/golangci-lint-action/workflows/build-and-test/badge.svg)](https://github.com/golangci/golangci-lint-action/actions)
![GitHub Annotations](./static/annotations.png) It's the official GitHub action for [golangci-lint](https://github.com/golangci/golangci-lint) from it's authors.
The action runs [golangci-lint](https://github.com/golangci/golangci-lint) and reports issues from linters.
The action that runs [golangci-lint](https://github.com/golangci/golangci-lint) and reports issues from linters. ![GitHub Annotations](./static/annotations.png)
## How to use ## How to use
1. Create a [GitHub token](https://github.com/settings/tokens/new) with scope `repo.public_repo`. Add `.github/workflows/golangci-lint.yml` with the following contents:
2. Add it to a [repository secrets](https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets): repository -> `Settings` -> `Secrets`.
3. Add `.github/workflows/golangci-lint.yml` with the following contents:
```yaml ```yaml
name: golangci-lint name: golangci-lint
@ -28,16 +27,13 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v1 uses: golangci/golangci-lint-action@v0.2.0
with: with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.26 version: v1.26
# Optional: golangci-lint command line arguments. # Optional: golangci-lint command line arguments.
# args: ./the-only-dir-to-analyze/... # args: ./the-only-dir-to-analyze/...
# Required: GitHub token with scope `repo.public_repo`. Used for fetching a list of releases of golangci-lint.
github-token: ${{ secrets.GOLANGCI_LINT_GITHUB_TOKEN }}
``` ```
## Comments and Annotations ## Comments and Annotations
@ -57,6 +53,13 @@ The action was implemented with performance in mind:
2. We don't use Docker because image pulling is slow. 2. We don't use Docker because image pulling is slow.
3. We do as much as we can in parallel, e.g. we download cache, go and golangci-lint binary in parallel. 3. We do as much as we can in parallel, e.g. we download cache, go and golangci-lint binary in parallel.
For example, in a repository of [golangci-lint](https://github.com/golangci/golangci-lint) running this action without the cache takes 50s, but with cache takes 14s:
* in parallel:
* 13s to download Go
* 4s to restore 50 MB of cache
* 1s to find and install `golangci-lint`
* 1s to run `golangci-lint` (it takes 35s without cache)
## Internals ## Internals
We use JavaScript-based action. We don't use Docker-based action because: We use JavaScript-based action. We don't use Docker-based action because:
@ -68,8 +71,8 @@ Inside our action we perform 3 steps:
1. Setup environment running in parallel: 1. Setup environment running in parallel:
* restore [cache](https://github.com/actions/cache) of previous analyzes * restore [cache](https://github.com/actions/cache) of previous analyzes
* list [releases of golangci-lint](https://github.com/golangci/golangci-lint/releases) and find the latest patch version * fetch [action config](https://github.com/golangci/golangci-lint/blob/master/assets/github-action-config.json) and find the latest `golangci-lint` patch version
for needed version (users of this action can specify only minor version). After that install [golangci-lint](https://github.com/golangci/golangci-lint) using [@actions/tool-cache](https://github.com/actions/toolkit/tree/master/packages/tool-cache) for needed version (users of this action can specify only minor version of `golangci-lint`). After that install [golangci-lint](https://github.com/golangci/golangci-lint) using [@actions/tool-cache](https://github.com/actions/toolkit/tree/master/packages/tool-cache)
* install the latest Go 1.x version using [@actions/setup-go](https://github.com/actions/setup-go) * install the latest Go 1.x version using [@actions/setup-go](https://github.com/actions/setup-go)
2. Run `golangci-lint` with specified by user `args` 2. Run `golangci-lint` with specified by user `args`
3. Save cache for later builds 3. Save cache for later builds
@ -87,6 +90,5 @@ This scheme is basic and needs improvements. Pull requests and ideas are welcome
1. Install [act](https://github.com/nektos/act#installation) 1. Install [act](https://github.com/nektos/act#installation)
2. Make a symlink for `act` to work properly: `ln -s . golangci-lint-action` 2. Make a symlink for `act` to work properly: `ln -s . golangci-lint-action`
3. Get a [GitHub token](https://github.com/settings/tokens/new) with the scope `repo.public_repo`. Export it by `export GITHUB_TOKEN=YOUR_TOKEN`. 3. Prepare deps once: `npm run prepare-deps`
4. Prepare deps once: `npm run prepare-deps` 4. Run `npm run local` after any change to test it
5. Run `npm run local` after any change to test it

View File

@ -1,6 +1,6 @@
--- ---
name: 'Run golangci-lint' name: 'Run golangci-lint'
description: 'Run golangci-lint (WIP)' description: 'Official golangci-lint action with line-attached annotations for found issues, caching and parallel execution. Beta version.'
author: 'golangci' author: 'golangci'
inputs: inputs:
version: version:
@ -19,5 +19,5 @@ runs:
main: 'dist/run/index.js' main: 'dist/run/index.js'
post: 'dist/post_run/index.js' post: 'dist/post_run/index.js'
branding: branding:
icon: 'check-circle' icon: 'shield'
color: 'blue' color: 'yellow'

24105
dist/post_run/index.js vendored

File diff suppressed because it is too large Load Diff

24105
dist/run/index.js vendored

File diff suppressed because it is too large Load Diff

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/golangci/golangci-lint-action
go 1.14

View File

@ -10,12 +10,12 @@
"prepare-deps": "npm run prepare-setup-go && npm run prepare-cache", "prepare-deps": "npm run prepare-setup-go && npm run prepare-cache",
"build": "tsc && ncc build -o dist/run/ src/main.ts && ncc build -o dist/post_run/ src/post_main.ts", "build": "tsc && ncc build -o dist/run/ src/main.ts && ncc build -o dist/post_run/ src/post_main.ts",
"type-check": "tsc", "type-check": "tsc",
"lint": "eslint **/*.ts --cache", "lint": "eslint --max-warnings 1 **/*.ts --cache",
"lint-fix": "eslint **/*.ts --cache --fix", "lint-fix": "eslint **/*.ts --cache --fix",
"format": "prettier --write **/*.ts", "format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts", "format-check": "prettier --check **/*.ts",
"all": "npm run build && npm run format-check && npm run lint", "all": "npm run build && npm run format-check && npm run lint",
"local": "npm run build && act -j test -s GOLANGCI_LINT_GITHUB_TOKEN=$GITHUB_TOKEN" "local": "npm run build && act -j test -b"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -4,7 +4,7 @@ import save from "cache/lib/save"
import * as crypto from "crypto" import * as crypto from "crypto"
import * as fs from "fs" import * as fs from "fs"
function checksumFile(hashName: string, path: string) { function checksumFile(hashName: string, path: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hash = crypto.createHash(hashName) const hash = crypto.createHash(hashName)
const stream = fs.createReadStream(path) const stream = fs.createReadStream(path)
@ -14,7 +14,7 @@ function checksumFile(hashName: string, path: string) {
}) })
} }
const pathExists = async (path: string) => !!(await fs.promises.stat(path).catch(e => false)) const pathExists = async (path: string): Promise<boolean> => !!(await fs.promises.stat(path).catch(() => false))
const getLintCacheDir = (): string => `${process.env.HOME}/.cache/golangci-lint` const getLintCacheDir = (): string => `${process.env.HOME}/.cache/golangci-lint`
@ -42,7 +42,7 @@ async function buildCacheKeys(): Promise<string[]> {
if (await pathExists(`go.mod`)) { if (await pathExists(`go.mod`)) {
// Add checksum to key to invalidate a cache when dependencies change. // Add checksum to key to invalidate a cache when dependencies change.
cacheKey += await checksumFile(`cache-key`, `go.mod`) cacheKey += await checksumFile(`sha1`, `go.mod`)
} else { } else {
cacheKey += `nogomod` cacheKey += `nogomod`
} }

View File

@ -3,17 +3,19 @@ import * as tc from "@actions/tool-cache"
import path from "path" import path from "path"
import { run as setupGo } from "setup-go/lib/main" import { run as setupGo } from "setup-go/lib/main"
import { stringifyVersion, Version } from "./version" import { VersionConfig } from "./version"
// The installLint returns path to installed binary of golangci-lint. // The installLint returns path to installed binary of golangci-lint.
export async function installLint(ver: Version): Promise<string> { export async function installLint(versionConfig: VersionConfig): Promise<string> {
core.info(`Installing golangci-lint ${stringifyVersion(ver)}...`) core.info(`Installing golangci-lint ${versionConfig.TargetVersion}...`)
const startedAt = Date.now() const startedAt = Date.now()
const dirName = `golangci-lint-${ver.major}.${ver.minor}.${ver.patch}-linux-amd64`
const assetUrl = `https://github.com/golangci/golangci-lint/releases/download/${stringifyVersion(ver)}/${dirName}.tar.gz`
const tarGzPath = await tc.downloadTool(assetUrl) core.info(`Downloading ${versionConfig.AssetURL} ...`)
const tarGzPath = await tc.downloadTool(versionConfig.AssetURL)
const extractedDir = await tc.extractTar(tarGzPath, process.env.HOME) const extractedDir = await tc.extractTar(tarGzPath, process.env.HOME)
const urlParts = versionConfig.AssetURL.split(`/`)
const dirName = urlParts[urlParts.length - 1].replace(/\.tar\.gz$/, ``)
const lintPath = path.join(extractedDir, dirName, `golangci-lint`) const lintPath = path.join(extractedDir, dirName, `golangci-lint`)
core.info(`Installed golangci-lint into ${lintPath} in ${Date.now() - startedAt}ms`) core.info(`Installed golangci-lint into ${lintPath} in ${Date.now() - startedAt}ms`)
return lintPath return lintPath

View File

@ -9,8 +9,8 @@ import { findLintVersion } from "./version"
const execShellCommand = promisify(exec) const execShellCommand = promisify(exec)
async function prepareLint(): Promise<string> { async function prepareLint(): Promise<string> {
const lintVersion = await findLintVersion() const versionConfig = await findLintVersion()
return await installLint(lintVersion) return await installLint(versionConfig)
} }
async function prepareEnv(): Promise<string> { async function prepareEnv(): Promise<string> {

View File

@ -1,24 +1,5 @@
import * as core from "@actions/core" import * as core from "@actions/core"
import * as github from "@actions/github" import * as httpm from "@actions/http-client"
import { Octokit } from "@actions/github/node_modules/@octokit/rest"
async function performResilientGitHubRequest<T>(opName: string, execFn: () => Promise<Octokit.Response<T>>): Promise<T> {
let lastError = ``
for (let i = 0; i < 3; i++) {
// TODO: configurable params, timeouts, random jitters, exponential back-off, etc
try {
const res = await execFn()
if (res.status === 200) {
return res.data
}
lastError = `GitHub returned HTTP code ${res.status}`
} catch (exc) {
lastError = exc.message
}
}
throw new Error(`failed to execute github operation '${opName}': ${lastError}`)
}
// TODO: make a class // TODO: make a class
export type Version = { export type Version = {
@ -84,40 +65,61 @@ const getRequestedLintVersion = (): Version => {
return parsedRequestedLintVersion return parsedRequestedLintVersion
} }
export async function findLintVersion(): Promise<Version> { export type VersionConfig = {
Error?: string
TargetVersion: string
AssetURL: string
}
type Config = {
MinorVersionToConfig: {
[minorVersion: string]: VersionConfig
}
}
const getConfig = async (): Promise<Config> => {
const http = new httpm.HttpClient(`golangci/golangci-lint-action`, [], {
allowRetries: true,
maxRetries: 5,
})
try {
const url = `https://raw.githubusercontent.com/golangci/golangci-lint/master/assets/github-action-config.json`
const response: httpm.HttpClientResponse = await http.get(url)
if (response.message.statusCode !== 200) {
throw new Error(`failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`)
}
const body = await response.readBody()
return JSON.parse(body)
} catch (exc) {
throw new Error(`failed to get action config: ${exc.message}`)
}
}
export async function findLintVersion(): Promise<VersionConfig> {
core.info(`Finding needed golangci-lint version...`) core.info(`Finding needed golangci-lint version...`)
const startedAt = Date.now() const startedAt = Date.now()
const reqLintVersion = getRequestedLintVersion() const reqLintVersion = getRequestedLintVersion()
const githubToken = core.getInput(`github-token`, { required: true }) const config = await getConfig()
const octokit = new github.GitHub(githubToken)
// TODO: fetch all pages, not only the first one. if (!config.MinorVersionToConfig) {
const releasesPage = await performResilientGitHubRequest(`fetch releases of golangci-lint`, function() { core.warning(JSON.stringify(config))
return octokit.repos.listReleases({ owner: `golangci`, repo: `golangci-lint`, [`per_page`]: 100 }) throw new Error(`invalid config: no MinorVersionToConfig field`)
})
// TODO: use semver and semver.satisfies
let latestPatchVersion: number | null = null
for (const rel of releasesPage) {
const ver = parseVersion(rel.tag_name)
if (ver.patch === null) {
// < minVersion
continue
}
if (ver.major == reqLintVersion.major && ver.minor == reqLintVersion.minor) {
latestPatchVersion = latestPatchVersion !== null ? Math.max(latestPatchVersion, ver.patch) : ver.patch
}
} }
if (latestPatchVersion === null) { const versionConfig = config.MinorVersionToConfig[stringifyVersion(reqLintVersion)]
throw new Error( if (!versionConfig) {
`requested golangci-lint lint version ${stringifyVersion(reqLintVersion)} doesn't exist in list of golangci-lint releases` throw new Error(`requested golangci-lint version '${stringifyVersion(reqLintVersion)}' doesn't exist`)
)
} }
const neededVersion = { ...reqLintVersion, patch: latestPatchVersion } if (versionConfig.Error) {
core.info(`Calculated needed golangci-lint version ${stringifyVersion(neededVersion)} in ${Date.now() - startedAt}ms`) throw new Error(`failed to use requested golangci-lint version '${stringifyVersion(reqLintVersion)}': ${versionConfig.Error}`)
return neededVersion }
core.info(
`Requested golangci-lint '${stringifyVersion(reqLintVersion)}', using '${versionConfig.TargetVersion}', calculation took ${Date.now() -
startedAt}ms`
)
return versionConfig
} }