Skip to content

Commit

Permalink
Merge branch 'paths-config' (fix #342, fix #217)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhysd committed Nov 2, 2024
2 parents d66fe00 + c7a09e0 commit 0c0a521
Show file tree
Hide file tree
Showing 18 changed files with 474 additions and 99 deletions.
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
actionlint
==========
[![CI Badge][]][CI]
[![API Document][api-badge]][apidoc]
[![API Document][apidoc-badge]][apidoc]

[actionlint][repo] is a static checker for GitHub Actions workflow files. [Try it online!][playground]

Expand Down Expand Up @@ -97,7 +97,7 @@ test.yaml:22:17: receiver of object dereference "permissions" must be type of ob
## Quick start

Install `actionlint` command by downloading [the released binary][releases] or by Homebrew or by `go install`. See
[the installation document](docs/install.md) for more details like how to manage the command with several package managers
[the installation document][install] for more details like how to manage the command with several package managers
or run via Docker container.

```sh
Expand All @@ -114,19 +114,19 @@ actionlint

Another option to try actionlint is [the online playground][playground]. Your browser can run actionlint through WebAssembly.

See [the usage document](docs/usage.md) for more details.
See [the usage document][usage] for more details.

## Documents

- [Checks][checks]: Full list of all checks done by actionlint with example inputs, outputs, and playground links.
- [Installation](docs/install.md): Installation instructions. Prebuilt binaries, a Docker image, building from
source, a download script (for CI), supports by several package managers are available.
- [Usage](docs/usage.md): How to use `actionlint` command locally or on GitHub Actions, the online playground, an official Docker
image, and integrations with reviewdog, Problem Matchers, super-linter, pre-commit, VS Code.
- [Configuration](docs/config.md): How to configure actionlint behavior. Currently, the labels of self-hosted runners and the
configuration variables can be set.
- [Go API](docs/api.md): How to use actionlint as Go library.
- [References](docs/reference.md): Links to resources.
- [Installation][install]: Installation instructions. Prebuilt binaries, a Docker image, building from source, a download script
(for CI), supports by several package managers are available.
- [Usage][usage]: How to use `actionlint` command locally or on GitHub Actions, the online playground, an official Docker image,
and integrations with reviewdog, Problem Matchers, super-linter, pre-commit, VS Code.
- [Configuration][config]: How to configure actionlint behavior. Currently, the labels of self-hosted runners, the configuration
variables, and ignore patterns of errors for each file paths can be set.
- [Go API][api]: How to use actionlint as Go library.
- [References][refs]: Links to resources.

## Bug reporting

Expand All @@ -139,7 +139,7 @@ actionlint is distributed under [the MIT license](./LICENSE.txt).

[CI Badge]: https://github.com/rhysd/actionlint/workflows/CI/badge.svg?branch=main&event=push
[CI]: https://github.com/rhysd/actionlint/actions?query=workflow%3ACI+branch%3Amain
[api-badge]: https://pkg.go.dev/badge/github.com/rhysd/actionlint.svg
[apidoc-badge]: https://pkg.go.dev/badge/github.com/rhysd/actionlint.svg
[apidoc]: https://pkg.go.dev/github.com/rhysd/actionlint
[repo]: https://github.com/rhysd/actionlint
[playground]: https://rhysd.github.io/actionlint/
Expand All @@ -149,6 +149,11 @@ actionlint is distributed under [the MIT license](./LICENSE.txt).
[syntax-doc]: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
[filter-pattern-doc]: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet
[script-injection-doc]: https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#understanding-the-risk-of-script-injections
[issue-form]: https://github.com/rhysd/actionlint/issues/new
[releases]: https://github.com/rhysd/actionlint/releases
[checks]: https://github.com/rhysd/actionlint/blob/v1.7.3/docs/checks.md
[install]: https://github.com/rhysd/actionlint/blob/v1.7.3/docs/install.md
[usage]: https://github.com/rhysd/actionlint/blob/v1.7.3/docs/usage.md
[config]: https://github.com/rhysd/actionlint/blob/v1.7.3/docs/config.md
[api]: https://github.com/rhysd/actionlint/blob/v1.7.3/docs/api.md
[refs]: https://github.com/rhysd/actionlint/blob/v1.7.3/docs/reference.md
[issue-form]: https://github.com/rhysd/actionlint/issues/new
4 changes: 2 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ func (cmd *Command) runLinter(args []string, opts *LinterOptions, initConfig boo
}

if initConfig {
return nil, l.GenerateDefaultConfig(".")
return nil, l.GenerateDefaultConfig("")
}

if len(args) == 0 {
return l.LintRepository(".")
return l.LintRepository("")
}

if len(args) == 1 && args[0] == "-" {
Expand Down
109 changes: 97 additions & 12 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,56 @@
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. They are used for ignoring errors by matching
// to 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 {
Expand All @@ -22,13 +64,40 @@ type Config struct {
// 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"`
}

func parseConfig(b []byte, path string) (*Config, error) {
// PathConfigsFor 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) PathConfigsFor(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, fmt.Errorf("could not parse config file %q: %s", path, msg)
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
}
Expand All @@ -39,23 +108,27 @@ func ReadConfigFile(path string) (*Config, error) {
if err != nil {
return nil, fmt.Errorf("could not read config file %q: %w", path, err)
}
return parseConfig(b, path)
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"} {
path := filepath.Join(root, ".github", f)
b, err := os.ReadFile(path)
if err != nil {
continue // file does not exist
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
}
cfg, err := parseConfig(b, path)
if err != nil {
return nil, err
}
return cfg, nil
}
return nil, nil
}
Expand All @@ -64,10 +137,22 @@ 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)
Expand Down
Loading

0 comments on commit 0c0a521

Please sign in to comment.