Skip to content

Commit 37ac79b

Browse files
authored
QUA-840: release golangci-lint (#1)
1 parent f1f0189 commit 37ac79b

File tree

9 files changed

+325
-203
lines changed

9 files changed

+325
-203
lines changed

Diff for: .circleci/config.yml

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
version: 2
2+
3+
jobs:
4+
build:
5+
machine:
6+
docker_layer_caching: true
7+
working_directory: ~/codeclimate/codeclimate-golangci-lint
8+
steps:
9+
- checkout
10+
- run: make image
11+
12+
release_images:
13+
machine:
14+
docker_layer_caching: true
15+
working_directory: ~/codeclimate/codeclimate-golangci-lint
16+
steps:
17+
- checkout
18+
- run:
19+
name: Validate owner
20+
command: |
21+
if [ "$CIRCLE_PROJECT_USERNAME" -ne "codeclimate" ]
22+
then
23+
echo "Skipping release for non-codeclimate branches"
24+
circleci step halt
25+
fi
26+
- run: make image
27+
- run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
28+
- run:
29+
name: Push image to Dockerhub
30+
command: |
31+
make release RELEASE_TAG="b$CIRCLE_BUILD_NUM"
32+
make release RELEASE_TAG="$(echo $CIRCLE_BRANCH | grep -oP 'channel/\K[\w\-]+')"
33+
34+
workflows:
35+
version: 2
36+
build_deploy:
37+
jobs:
38+
- build
39+
- release_images:
40+
context: Quality
41+
requires:
42+
- build
43+
filters:
44+
branches:
45+
only: /master|channel\/[\w-]+/
46+
notify:
47+
webhooks:
48+
- url: https://cc-slack-proxy.herokuapp.com/circle

Diff for: Dockerfile

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
FROM golang:latest AS build
1+
FROM golang:1.20.3-alpine3.17 AS build
22

33
# Install dependencies:
4-
RUN apt-get update && apt-get -y install curl
5-
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /usr/local/bin v1.45.2
4+
RUN apk update && apk add curl
5+
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b /usr/local/bin v1.52.2
66

7-
FROM golang:latest
7+
FROM golang:1.20.3-alpine3.17
88
LABEL maintainer="Victor \"Vito\" Gama <[email protected]>"
99

10-
RUN apt-get update && apt-get -y install build-essential ruby && rm -rf /var/cache/apt/archives /var/lib/apt/lists/*.
10+
RUN apk update && apk add \
11+
ruby && rm -rf /var/cache/apt/archives /var/lib/apt/lists/*.
1112

1213
WORKDIR /usr/src/app/
1314

1415
COPY --from=build /usr/local/bin/golangci-lint /usr/local/bin/golangci-lint
1516
COPY engine.json /
1617

17-
RUN adduser -u 9000 --shell /bin/false app
18+
RUN adduser -S -u 9000 --shell /bin/false app
1819
USER app
1920

2021
COPY . ./
2122

2223
VOLUME /code
2324
WORKDIR /code
2425

25-
CMD ["/usr/src/app/entrypoint.sh"]
26+
CMD ["/usr/src/app/bin/codeclimate-golangci-lint"]

Diff for: Makefile

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
.PHONY: image
1+
.PHONY: image test docs bundle release
22

3-
IMAGE_NAME ?= codeclimate/codeclimate-golangci
3+
IMAGE_NAME ?= codeclimate/codeclimate-golangci-lint
4+
RELEASE_REGISTRY ?= codeclimate
5+
6+
ifndef RELEASE_TAG
7+
override RELEASE_TAG = latest
8+
endif
49

510
image:
611
docker build --rm -t $(IMAGE_NAME) .
12+
13+
release:
14+
docker tag $(IMAGE_NAME) $(RELEASE_REGISTRY)/codeclimate-golangci-lint:$(RELEASE_TAG)
15+
docker push $(RELEASE_REGISTRY)/codeclimate-golangci-lint:$(RELEASE_TAG)

Diff for: bin/codeclimate-golangci-lint

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env ruby
2+
3+
$:.unshift(
4+
File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
5+
)
6+
7+
require 'cc/engine/golangci'
8+
9+
engine_config =
10+
if File.exist?("/config.json")
11+
JSON.parse(File.read("/config.json"))
12+
elsif File.exist?("/code/config.json")
13+
JSON.parse(File.read("/code/config.json"))
14+
else
15+
{}
16+
end
17+
18+
CC::Engine::Golangci.new(engine_config).run

Diff for: entrypoint.sh

-5
This file was deleted.

Diff for: lib/cc/engine/categories.rb

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
module CC
2+
module Engine
3+
class Golangci
4+
VALID_CATEGORIES = [
5+
BUG_RISK = 'Bug Risk',
6+
CLARITY = 'Clarity',
7+
COMPATIBILITY = 'Compatibility',
8+
COMPLEXITY = 'Complexity',
9+
DUPLICATION = 'Duplication',
10+
PERFORMANCE = 'Performance',
11+
SECURITY = 'Security',
12+
STYLE = 'Style'
13+
].freeze
14+
15+
CATEGORIES = {
16+
'asciicheck' => [CLARITY, BUG_RISK],
17+
'bidichk' => [BUG_RISK],
18+
'bodyclose' => [PERFORMANCE, BUG_RISK],
19+
'containedctx' => [STYLE],
20+
'contextcheck' => [BUG_RISK],
21+
'cyclop' => [COMPLEXITY],
22+
'deadcode' => [CLARITY],
23+
'decorder' => [CLARITY, STYLE],
24+
'depguard' => [STYLE],
25+
'dogsled' => [STYLE],
26+
'dupl' => [DUPLICATION],
27+
'durationcheck' => [BUG_RISK],
28+
'errcheck' => [BUG_RISK],
29+
'errchkjson' => [BUG_RISK],
30+
'errname' => [STYLE],
31+
'errorlint' => [BUG_RISK],
32+
'execinquery' => [SECURITY, BUG_RISK],
33+
'exhaustive' => [BUG_RISK],
34+
'exhaustivestruct' => [STYLE, BUG_RISK],
35+
'exportloopref' => [BUG_RISK],
36+
'forbidigo' => [STYLE],
37+
'forcetypeassert' => [STYLE, BUG_RISK],
38+
'funlen' => [COMPLEXITY],
39+
'gci' => [COMPATIBILITY, STYLE],
40+
'gochecknoglobals' => [STYLE],
41+
'gochecknoinits' => [STYLE],
42+
'gocognit' => [COMPLEXITY],
43+
'goconst' => [STYLE],
44+
'gocritic' => [BUG_RISK, PERFORMANCE, STYLE],
45+
'gocyclo' => [COMPLEXITY],
46+
'godot' => [STYLE],
47+
'godox' => [STYLE, BUG_RISK],
48+
'goerr113' => [STYLE],
49+
'gofmt' => [STYLE],
50+
'gofumpt' => [STYLE],
51+
'goheader' => [STYLE],
52+
'goimports' => [STYLE],
53+
'golint' => [STYLE],
54+
'gomnd' => [STYLE, CLARITY],
55+
'gomoddirectives' => [STYLE],
56+
'gomodguard' => [STYLE, COMPATIBILITY],
57+
'goprintffuncname' => [STYLE],
58+
'gosec' => [SECURITY, BUG_RISK],
59+
'gosimple' => [STYLE],
60+
'govet' => [BUG_RISK],
61+
'grouper' => [STYLE],
62+
'ifshort' => [STYLE],
63+
'importas' => [STYLE],
64+
'ineffassign' => [CLARITY],
65+
'interfacer' => [STYLE],
66+
'ireturn' => [STYLE],
67+
'lll' => [STYLE],
68+
'maintidx' => [STYLE, COMPLEXITY],
69+
'makezero' => [STYLE, BUG_RISK, PERFORMANCE],
70+
'maligned' => [PERFORMANCE],
71+
'misspell' => [STYLE],
72+
'nakedret' => [STYLE, BUG_RISK],
73+
'nestif' => [STYLE, CLARITY],
74+
'nilerr' => [BUG_RISK],
75+
'nilnil' => [STYLE, BUG_RISK],
76+
'nlreturn' => [STYLE],
77+
'noctx' => [PERFORMANCE, BUG_RISK],
78+
'nolintlint' => [STYLE],
79+
'paralleltest' => [STYLE],
80+
'prealloc' => [PERFORMANCE],
81+
'predeclared' => [STYLE, CLARITY],
82+
'promlinter' => [STYLE],
83+
'revive' => [STYLE],
84+
'rowserrcheck' => [BUG_RISK],
85+
'scopelint' => [BUG_RISK],
86+
'sqlclosecheck' => [BUG_RISK, PERFORMANCE],
87+
'staticcheck' => [BUG_RISK],
88+
'structcheck' => [CLARITY],
89+
'stylecheck' => [STYLE],
90+
'tagliatelle' => [STYLE],
91+
'tenv' => [STYLE, BUG_RISK],
92+
'testpackage' => [STYLE],
93+
'thelper' => [STYLE],
94+
'tparallel' => [STYLE],
95+
'typecheck' => [BUG_RISK],
96+
'unconvert' => [STYLE],
97+
'unparam' => [STYLE, CLARITY],
98+
'unused' => [CLARITY],
99+
'varcheck' => [CLARITY],
100+
'varnamelen' => [STYLE],
101+
'wastedassign' => [STYLE],
102+
'whitespace' => [STYLE],
103+
'wrapcheck' => [STYLE],
104+
'wsl' => [STYLE]
105+
}.freeze
106+
end
107+
end
108+
end

Diff for: lib/cc/engine/golangci.rb

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
require_relative 'categories'
2+
require 'pathname'
3+
require 'json'
4+
require 'digest/sha1'
5+
6+
module CC
7+
module Engine
8+
class Golangci
9+
ALLOWED_EXTENSIONS = %w[.go]
10+
11+
def initialize(engine_config)
12+
@engine_config = engine_config || {}
13+
@files_to_analyze = []
14+
@dirs_to_analyze = []
15+
end
16+
17+
attr_reader :engine_config, :dirs_to_analyze, :files_to_analyze
18+
19+
def run
20+
build_paths_to_analyze
21+
22+
run_for_paths(dirs_to_analyze)
23+
run_for_paths(files_to_analyze)
24+
end
25+
26+
def run_for_paths(paths)
27+
data = IO.popen(command_env, command(paths)).read
28+
begin
29+
data = JSON.parse(data)
30+
rescue JSON::ParserError
31+
warn "Error parsing golangci-lint's output:"
32+
warn data
33+
exit!
34+
end
35+
36+
issues = data["Issues"]
37+
return unless issues.is_a?(Array) && issues.length > 0
38+
39+
puts data['Issues'].map { |issue| "#{convert_issue(issue)}\0" }.join
40+
end
41+
42+
def command(paths)
43+
["/usr/local/bin/golangci-lint", "run", "--out-format", "json", *paths]
44+
end
45+
46+
def command_env
47+
{ "CGO_ENABLED" => "0" }
48+
end
49+
50+
ISSUE_IDENTIFIER_REGEXP = /^([^\s]+): (.*)/.freeze
51+
52+
def convert_issue(issue)
53+
text = issue['Text']
54+
# Data coming from linters is not standardised, so it may be quite
55+
# complicated to extract a check_name and description from it. Here we
56+
# try to obtain something that resembles an identifier in a best effort
57+
# fashion.
58+
check_name = text
59+
linter_name = issue['FromLinter']
60+
61+
unless (m = ISSUE_IDENTIFIER_REGEXP.match(text)).nil?
62+
check_name = m[1]
63+
end
64+
65+
{
66+
type: :issue,
67+
check_name: check_name,
68+
description: "#{linter_name}: #{text}",
69+
categories: categories_for_linter(linter_name),
70+
fingerprint: fingerprint_issue(issue),
71+
location: locate_issue(issue)
72+
}.to_json
73+
end
74+
75+
def categories_for_linter(linter)
76+
CATEGORIES[linter]
77+
end
78+
79+
def locate_issue(issue)
80+
pos = issue['Pos']
81+
{
82+
path: pos['Filename'],
83+
positions: {
84+
begin: {
85+
line: pos['Line'],
86+
column: pos['Column']
87+
},
88+
end: {
89+
line: pos['Line'] + issue['SourceLines'].length,
90+
column: issue['SourceLines'].last.length
91+
}
92+
}
93+
}
94+
end
95+
96+
def fingerprint_issue(issue)
97+
data = [
98+
issue.dig('Pos', 'Filename'),
99+
issue['Text'],
100+
issue['SourceLines'].first
101+
].join('')
102+
Digest::SHA1.hexdigest data
103+
end
104+
105+
private
106+
107+
def build_paths_to_analyze
108+
# golangci-lint surfaces errors when analyzing directories and files in the same run,
109+
# so we need to split the analysis into two different runs: one for directories and one for files
110+
111+
include_paths = engine_config["include_paths"] || ["./"]
112+
113+
include_paths.each do |path|
114+
begin
115+
pathname = Pathname.new(path).realpath
116+
117+
if pathname.directory?
118+
# golangci-lint allows adding ... to a directory path to analyze it recursively
119+
# we want to do this for all directories
120+
121+
@dirs_to_analyze << (pathname + "...").to_s
122+
else
123+
@files_to_analyze << pathname.to_s if ALLOWED_EXTENSIONS.include?(pathname.extname)
124+
end
125+
rescue Errno::ENOENT
126+
nil
127+
end
128+
end.compact
129+
end
130+
end
131+
end
132+
end

0 commit comments

Comments
 (0)