Skip to content

Commit 6ecd88d

Browse files
[UPDATE] Add pre-commit hooks to catch linting issues (#2098)
1 parent 22dbc7f commit 6ecd88d

File tree

9 files changed

+450
-0
lines changed

9 files changed

+450
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
hooks:
2+
common:
3+
- repo: https://github.com/pre-commit/pre-commit-hooks
4+
rev: v4.4.0
5+
hooks:
6+
- id: end-of-file-fixer
7+
- id: trailing-whitespace
8+
- repo: https://github.com/gitleaks/gitleaks
9+
rev: v8.16.3
10+
hooks:
11+
- id: gitleaks
12+
Python:
13+
- repo: https://github.com/pylint-dev/pylint
14+
rev: v2.17.2
15+
hooks:
16+
- id: pylint
17+
JavaScript:
18+
- repo: https://github.com/pre-commit/mirrors-eslint
19+
rev: v8.38.0
20+
hooks:
21+
- id: eslint
22+
TypeScript:
23+
- repo: https://github.com/pre-commit/mirrors-eslint
24+
rev: v8.38.0
25+
hooks:
26+
- id: eslint
27+
Java:
28+
- repo: https://github.com/gherynos/pre-commit-java
29+
rev: v0.2.4
30+
hooks:
31+
- id: Checkstyle
32+
C:
33+
- repo: https://github.com/pocc/pre-commit-hooks
34+
rev: v1.3.5
35+
hooks:
36+
- id: cpplint
37+
C++:
38+
- repo: https://github.com/pocc/pre-commit-hooks
39+
rev: v1.3.5
40+
hooks:
41+
- id: cpplint
42+
PHP:
43+
- repo: https://github.com/digitalpulp/pre-commit-php
44+
rev: 1.4.0
45+
hooks:
46+
- id: php-lint-all
47+
Ruby:
48+
- repo: https://github.com/jumanjihouse/pre-commit-hooks
49+
rev: 3.0.0
50+
hooks:
51+
- id: RuboCop
52+
Go:
53+
- repo: https://github.com/golangci/golangci-lint
54+
rev: v1.52.2
55+
hooks:
56+
- id: golangci-lint
57+
Shell:
58+
- repo: https://github.com/jumanjihouse/pre-commit-hooks
59+
rev: 3.0.0
60+
hooks:
61+
- id: shellcheck
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package precommit
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"path"
9+
"sort"
10+
"strings"
11+
12+
"github.com/step-security/secure-repo/remediation/workflow/permissions"
13+
"gopkg.in/yaml.v3"
14+
)
15+
16+
type UpdatePrecommitConfigResponse struct {
17+
OriginalInput string
18+
FinalOutput string
19+
IsChanged bool
20+
ConfigfileFetchError bool
21+
}
22+
23+
type UpdatePrecommitConfigRequest struct {
24+
Content string
25+
Languages []string
26+
}
27+
28+
type PrecommitConfig struct {
29+
Repos []Repo `yaml:"repos"`
30+
}
31+
32+
type Repo struct {
33+
Repo string `yaml:"repo"`
34+
Rev string `yaml:"rev"`
35+
Hooks []Hook `yaml:"hooks"`
36+
}
37+
38+
type Hook struct {
39+
Id string `yaml:"id"`
40+
}
41+
42+
type FetchPrecommitConfig struct {
43+
Hooks Hooks `yaml:"hooks"`
44+
}
45+
46+
// type LangHook struct {
47+
// Repo Repo `yaml:"hook"`
48+
// }
49+
50+
type Hooks map[string][]Repo
51+
52+
func getConfigFile() (string, error) {
53+
filePath := os.Getenv("PRECOMMIT_CONFIG")
54+
55+
if filePath == "" {
56+
filePath = "./"
57+
}
58+
59+
configFile, err := ioutil.ReadFile(path.Join(filePath, "precommit-config.yml"))
60+
if err != nil {
61+
return "", err
62+
}
63+
64+
return string(configFile), nil
65+
}
66+
67+
func GetHooks(languages []string, alreadyPresentHooks map[string]bool) ([]Repo, error) {
68+
configFile, err := getConfigFile()
69+
if err != nil {
70+
return nil, err
71+
}
72+
var fetchPrecommitConfig FetchPrecommitConfig
73+
yaml.Unmarshal([]byte(configFile), &fetchPrecommitConfig)
74+
newHooks := make(map[string]Repo)
75+
for _, lang := range languages {
76+
if _, ok := alreadyPresentHooks[fetchPrecommitConfig.Hooks[lang][0].Hooks[0].Id]; !ok {
77+
if repo, ok := newHooks[fetchPrecommitConfig.Hooks[lang][0].Repo]; ok {
78+
repo.Hooks = append(repo.Hooks, fetchPrecommitConfig.Hooks[lang][0].Hooks...)
79+
newHooks[fetchPrecommitConfig.Hooks[lang][0].Repo] = repo
80+
} else {
81+
newHooks[fetchPrecommitConfig.Hooks[lang][0].Repo] = fetchPrecommitConfig.Hooks[lang][0]
82+
}
83+
alreadyPresentHooks[fetchPrecommitConfig.Hooks[lang][0].Hooks[0].Id] = true
84+
}
85+
}
86+
// Adding common hooks
87+
var repos []Repo
88+
for _, repo := range fetchPrecommitConfig.Hooks["common"] {
89+
tempRepo := repo
90+
tempRepo.Hooks = nil
91+
hookPresent := false
92+
for _, hook := range repo.Hooks {
93+
if _, ok := alreadyPresentHooks[hook.Id]; !ok {
94+
tempRepo.Hooks = append(tempRepo.Hooks, hook)
95+
hookPresent = true
96+
}
97+
}
98+
if hookPresent {
99+
repos = append(repos, tempRepo)
100+
}
101+
}
102+
for _, repo := range newHooks {
103+
repos = append(repos, repo)
104+
}
105+
sort.Slice(repos, func(i, j int) bool {
106+
return repos[i].Repo < repos[j].Repo
107+
})
108+
return repos, nil
109+
}
110+
111+
func UpdatePrecommitConfig(precommitConfig string) (*UpdatePrecommitConfigResponse, error) {
112+
var updatePrecommitConfigRequest UpdatePrecommitConfigRequest
113+
json.Unmarshal([]byte(precommitConfig), &updatePrecommitConfigRequest)
114+
inputConfigFile := []byte(updatePrecommitConfigRequest.Content)
115+
configMetadata := PrecommitConfig{}
116+
err := yaml.Unmarshal(inputConfigFile, &configMetadata)
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
response := new(UpdatePrecommitConfigResponse)
122+
response.FinalOutput = updatePrecommitConfigRequest.Content
123+
response.OriginalInput = updatePrecommitConfigRequest.Content
124+
response.IsChanged = false
125+
126+
if updatePrecommitConfigRequest.Content == "" {
127+
response.FinalOutput = "repos:"
128+
}
129+
130+
alreadyPresentHooks := make(map[string]bool)
131+
for _, repos := range configMetadata.Repos {
132+
for _, hook := range repos.Hooks {
133+
alreadyPresentHooks[hook.Id] = true
134+
}
135+
}
136+
// Contains a list of hooks to be added and not present in the file
137+
Hooks, err := GetHooks(updatePrecommitConfigRequest.Languages, alreadyPresentHooks)
138+
if err != nil {
139+
return nil, err
140+
}
141+
142+
for _, Update := range Hooks {
143+
repoAlreadyExist := false
144+
for _, update := range configMetadata.Repos {
145+
if update.Repo == Update.Repo {
146+
repoAlreadyExist = true
147+
}
148+
if repoAlreadyExist {
149+
break
150+
}
151+
}
152+
response.FinalOutput, err = addHook(Update, repoAlreadyExist, response.FinalOutput)
153+
if err != nil {
154+
return nil, err
155+
}
156+
response.IsChanged = true
157+
}
158+
159+
return response, nil
160+
}
161+
162+
func addHook(Update Repo, repoAlreadyExist bool, inputYaml string) (string, error) {
163+
t := yaml.Node{}
164+
165+
err := yaml.Unmarshal([]byte(inputYaml), &t)
166+
if err != nil {
167+
return "", fmt.Errorf("unable to parse yaml %v", err)
168+
}
169+
170+
spaces := ""
171+
jobNode := permissions.IterateNode(&t, "hooks", "!!seq", 0)
172+
if jobNode == nil {
173+
spaces = " "
174+
} else {
175+
for i := 0; i < jobNode.Column-1; i++ {
176+
spaces += " "
177+
}
178+
}
179+
180+
if repoAlreadyExist {
181+
jobNode = permissions.IterateNode(&t, Update.Repo, "!!str", 0)
182+
if jobNode == nil {
183+
return "", fmt.Errorf("Repo Name %s not found in the input yaml", Update.Repo)
184+
}
185+
186+
// TODO: Also update rev version for already exist repo
187+
inputLines := strings.Split(inputYaml, "\n")
188+
var output []string
189+
for i := 0; i < jobNode.Line+1; i++ {
190+
output = append(output, inputLines[i])
191+
}
192+
193+
for _, hook := range Update.Hooks {
194+
output = append(output, spaces+fmt.Sprintf("- id: %s", hook.Id))
195+
}
196+
197+
for i := jobNode.Line + 1; i < len(inputLines); i++ {
198+
output = append(output, inputLines[i])
199+
}
200+
return strings.Join(output, "\n"), nil
201+
} else {
202+
inputLines := strings.Split(inputYaml, "\n")
203+
inputLines = append(inputLines, fmt.Sprintf("- repo: %s", Update.Repo))
204+
inputLines = append(inputLines, fmt.Sprintf(" rev: %s", Update.Rev))
205+
inputLines = append(inputLines, fmt.Sprintf(" hooks:"))
206+
207+
for _, hook := range Update.Hooks {
208+
inputLines = append(inputLines, spaces+fmt.Sprintf("- id: %s", hook.Id))
209+
}
210+
return strings.Join(inputLines, "\n"), nil
211+
}
212+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package precommit
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"log"
7+
"path"
8+
"testing"
9+
)
10+
11+
func TestUpdatePrecommitConfig(t *testing.T) {
12+
13+
const inputDirectory = "../../testfiles/precommit/input"
14+
const outputDirectory = "../../testfiles/precommit/output"
15+
16+
tests := []struct {
17+
fileName string
18+
Languages []string
19+
isChanged bool
20+
}{
21+
{
22+
fileName: "basic.yml",
23+
Languages: []string{"JavaScript", "C++"},
24+
isChanged: true,
25+
},
26+
{
27+
fileName: "file-not-exit.yml",
28+
Languages: []string{"JavaScript", "C++"},
29+
isChanged: true,
30+
},
31+
{
32+
fileName: "same-repo-different-hooks.yml",
33+
Languages: []string{"Ruby", "Shell"},
34+
isChanged: true,
35+
},
36+
}
37+
38+
for _, test := range tests {
39+
var updatePrecommitConfigRequest UpdatePrecommitConfigRequest
40+
input, err := ioutil.ReadFile(path.Join(inputDirectory, test.fileName))
41+
if err != nil {
42+
log.Fatal(err)
43+
}
44+
updatePrecommitConfigRequest.Content = string(input)
45+
updatePrecommitConfigRequest.Languages = test.Languages
46+
inputRequest, err := json.Marshal(updatePrecommitConfigRequest)
47+
if err != nil {
48+
log.Fatal(err)
49+
}
50+
51+
output, err := UpdatePrecommitConfig(string(inputRequest))
52+
if err != nil {
53+
t.Fatalf("Error not expected: %s", err)
54+
}
55+
56+
expectedOutput, err := ioutil.ReadFile(path.Join(outputDirectory, test.fileName))
57+
if err != nil {
58+
log.Fatal(err)
59+
}
60+
61+
if string(expectedOutput) != output.FinalOutput {
62+
t.Errorf("test failed %s did not match expected output\n%s", test.fileName, output.FinalOutput)
63+
}
64+
65+
if output.IsChanged != test.isChanged {
66+
t.Errorf("test failed %s did not match IsChanged, Expected: %v Got: %v", test.fileName, test.isChanged, output.IsChanged)
67+
68+
}
69+
70+
}
71+
72+
}

testfiles/precommit/input/basic.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v2.3.0
4+
hooks:
5+
- id: check-yaml
6+
- id: check-json
7+
- id: trailing-whitespace
8+
- repo: https://github.com/pre-commit/mirrors-eslint
9+
rev: v8.23.0
10+
hooks:
11+
- id: eslint
12+
- repo: https://github.com/ejba/pre-commit-maven
13+
rev: v0.3.3
14+
hooks:
15+
- id: maven-test
16+
- repo: https://github.com/zricethezav/gitleaks
17+
rev: v8.12.0
18+
hooks:
19+
- id: gitleaks

testfiles/precommit/input/file-not-exit.yml

Whitespace-only changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v2.3.0
4+
hooks:
5+
- id: check-yaml
6+
- id: check-json
7+
- id: trailing-whitespace
8+
- repo: https://github.com/pre-commit/mirrors-eslint
9+
rev: v8.23.0
10+
hooks:
11+
- id: eslint
12+
- repo: https://github.com/ejba/pre-commit-maven
13+
rev: v0.3.3
14+
hooks:
15+
- id: maven-test
16+
- repo: https://github.com/zricethezav/gitleaks
17+
rev: v8.12.0
18+
hooks:
19+
- id: gitleaks

0 commit comments

Comments
 (0)