-
Notifications
You must be signed in to change notification settings - Fork 158
/
config.go
161 lines (146 loc) · 5.26 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package actionlint
import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/bmatcuk/doublestar/v4"
"gopkg.in/yaml.v3"
)
// IgnorePatterns is a list of regular expressions. These patterns are used for filtering errors by
// matching the error messages.
type IgnorePatterns []*regexp.Regexp
// Match returns whether the given error should be ignored due to the "ignore" configuration.
func (pats IgnorePatterns) Match(err *Error) bool {
for _, r := range pats {
if r.MatchString(err.Message) {
return true
}
}
return false
}
// UnmarshalYAML implements yaml.Unmarshaler.
func (pats *IgnorePatterns) UnmarshalYAML(n *yaml.Node) error {
if n.Kind != yaml.SequenceNode {
return fmt.Errorf("yaml: \"ignore\" must be a sequence node at line:%d,col:%d", n.Line, n.Column)
}
rs := make([]*regexp.Regexp, 0, len(n.Content))
for _, p := range n.Content {
r, err := regexp.Compile(p.Value)
if err != nil {
return fmt.Errorf("invalid regular expression %q in \"ignore\" at line%d,col:%d: %w", p.Value, n.Line, n.Column, err)
}
rs = append(rs, r)
}
*pats = rs
return nil
}
// PathConfig is a configuration for specific file path pattern. This is for values of the "paths" mapping
// in the configuration file.
type PathConfig struct {
// Ignore is a list of patterns. They are used for ignoring errors by matching to the error messages.
// It is similar to the "-ignore" command line option.
Ignore IgnorePatterns `yaml:"ignore"`
}
// Config is configuration of actionlint. This struct instance is parsed from "actionlint.yaml"
// file usually put in ".github" directory.
type Config struct {
// SelfHostedRunner is configuration for self-hosted runner.
SelfHostedRunner struct {
// Labels is label names for self-hosted runner.
Labels []string `yaml:"labels"`
} `yaml:"self-hosted-runner"`
// ConfigVariables is names of configuration variables used in the checked workflows. When this value is nil,
// property names of `vars` context will not be checked. Otherwise actionlint will report a name which is not
// listed here as undefined config variables.
// https://docs.github.com/en/actions/learn-github-actions/variables
ConfigVariables []string `yaml:"config-variables"`
// Paths is a "paths" mapping in the configuration file. The keys are glob patterns to match file paths.
// And the values are corresponding configurations applied to the file paths.
Paths map[string]PathConfig `yaml:"paths"`
}
// PathConfigs returns a list of all PathConfig values matching to the given file path. The path must
// be relative to the root of the project.
func (cfg *Config) PathConfigs(path string) []PathConfig {
path = filepath.ToSlash(path)
var ret []PathConfig
if cfg != nil {
for p, c := range cfg.Paths {
// Glob patterns were validated in `ParseConfig()`
if doublestar.MatchUnvalidated(p, path) {
ret = append(ret, c)
}
}
}
return ret
}
// ParseConfig parses the given bytes as an actionlint config file. When deserializing the YAML file
// or the config validation fails, this function returns an error.
func ParseConfig(b []byte) (*Config, error) {
var c Config
if err := yaml.Unmarshal(b, &c); err != nil {
msg := strings.ReplaceAll(err.Error(), "\n", " ")
return nil, errors.New(msg)
}
for pat := range c.Paths {
if !doublestar.ValidatePattern(pat) {
return nil, fmt.Errorf("invalid glob pattern %q in \"paths\"", pat)
}
}
return &c, nil
}
// ReadConfigFile reads actionlint config file (actionlint.yaml) from the given file path.
func ReadConfigFile(path string) (*Config, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("could not read config file %q: %w", path, err)
}
c, err := ParseConfig(b)
if err != nil {
return nil, fmt.Errorf("could not parse config file %q: %w", path, err)
}
return c, nil
}
// loadRepoConfig reads config file from the repository's .github/actionlint.yml or
// .github/actionlint.yaml.
func loadRepoConfig(root string) (*Config, error) {
for _, f := range []string{"actionlint.yaml", "actionlint.yml"} {
p := filepath.Join(root, ".github", f)
c, err := ReadConfigFile(p)
switch {
case errors.Is(err, os.ErrNotExist):
continue
case err != nil:
return nil, fmt.Errorf("could not parse config file %q: %w", p, err)
default:
return c, nil
}
}
return nil, nil
}
func writeDefaultConfigFile(path string) error {
b := []byte(`self-hosted-runner:
# Labels of self-hosted runner in array of strings.
labels: []
# Configuration variables in array of strings defined in your repository or
# organization. ` + "`null`" + ` means disabling configuration variables check.
# Empty array means no configuration variable is allowed.
config-variables: null
# Configuration for file paths. The keys are glob patterns to match to file
# paths relative to the repository root. The values are the configurations for
# the file paths. Note that the path separator is always '/'.
# The following configurations are available.
#
# "ignore" is an array of regular expression patterns. Matched error messages
# are ignored. This is similar to the "-ignore" command line option.
paths:
# .github/workflows/**/*.yml:
# ignore: []
`)
if err := os.WriteFile(path, b, 0644); err != nil {
return fmt.Errorf("could not write default configuration file at %q: %w", path, err)
}
return nil
}