Skip to content

joseph-flinn/github-actions-workflow-linter

Repository files navigation

GitHub Actions Workflow Linter (gawl)

GitHub Actions Workflow Linter is an extensible linter to apply opinionated GitHub Action standards. It was designed to be used alongside yamllint to enforce specific YAML standards.

Installation

From GitHub Release

Not yet implemented

Locally

git clone git@github.com:joseph-flinn/workflow-linter.git
cd workflow-linter

pip install -e .

Usage

Notable Features

  • Styalistic linting to improve GitHub Action maintenance
  • Supply-chain attack protection
    • Enables approved pinned versions of actions
    • Enables easy updates to pinned versions
    • Enables security audit/workflow for approved GitHub Action version updates
    • Provides option to auto-approve internal actions (assuming best security practices)

Setup settings.yaml

If a non-default configuration is desired (different than src/github_actions_workflow_linter/default_settings.yaml), create a settings.yaml in the directory that gawl will be running from.

Name Type Description
enabled_rules list Installed Python modules that follow the Rule model. Defaults are found in github_actions_workflow_linter.rules
approved_actions_path string A path to the JSON file of approved actions, following gawl's schema
internal_actions.enabled bool Enable the "Internal Actions" feature behavior, granting more trust to org actions
internal_actions.org string The name of the GitHub org from which to trust actions
internal_actions.repos list The names of GitHub repos allowed for hosting internal actions
# full_settings.yaml example
enabled_rules:
    - github_actions_workflow_linter.rules.name_exists.RuleNameExists
    - github_actions_workflow_linter.rules.name_capitalized.RuleNameCapitalized
    - github_actions_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned
    - github_actions_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix
    - github_actions_workflow_linter.rules.step_pinned.RuleStepUsesPinned
    - github_actions_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs

approved_actions_path: default_actions.json

internal_actions:
  enabled: True
  org_name: "flinnsolutions"
  repo_name: "gh-actions"
usage: ghwl [-h] [-v] {lint,actions} ...

positional arguments:
  {lint,actions}
    lint          Verify that a GitHub Action Workflow follows all of the Rules.
    actions       Add or Update Actions in the pre-approved list.

options:
  -h, --help      show this help message and exit
  -v, --verbose

Development

Requirements

  • Python 3.11
  • pipenv
  • Windows systems: Chocolatey package manager
  • Mac OS systems: Homebrew package manager

Setup

pipenv install --dev
pipenv shell

Testing

All built-in src/github_actions_workflow_linter/rules should have 100% code coverage and we should shoot for an overall coverage of 80%+. We are lax on the imperative shell (code interacting with other systems; ie. disk, network, etc), but we strive to maintain a high coverage over the functional core (objects and models).

pipenv shell
pytest tests --cov=src

Code Reformatting

We adhere to PEP8 and use black to maintain this adherence. black should be run on any change being merged to main.

pipenv shell
black .

Linting

We loosely use Google's Python style guide, but yield to black when there is a conflict

pipenv shell
pylint --rcfile pylintrc src/ tests/

Add a new Rule

A new Rule is created by extending the Rule base class and overriding the fn(obj: Union[Workflow, Job, Step]) method. Available attributes of Workflows, Jobs and Steps can be found in their definitons under src/models.

For a simple example, we'll take a look at enforcing the existence of the name key in a Job. This is already done by default with the src.rules.name_exists.RuleNameExists, but provides a simple enough example to walk through.

from typing import Union, Tuple

from ..rule import Rule
from ..models.job import Job
from ..models.workflow import Workflow
from ..models.step import Step
from ..utils import LintLevels, Settings


class RuleJobNameExists(Rule):
    def __init__(self, settings: Settings = None) -> None:
        self.message = "name must exist"
        self.on_fail: LintLevels = LintLevels.ERROR
        self.compatibility: List[Union[Workflow, Job, Step]] = [Job]
        self.settings: Settings = settings

    def fn(self, obj: Job) -> Tuple[bool, str]:
        """<doc block goes here> """
        if obj.name is not None:
            return True, ""
        return False, self.message

By default, a new Rule needs five things:

  • self.message: The message to return to the user on a lint failure
  • self.on_fail: The level of failure on a lint failure (NONE, WARNING, ERROR). NONE and WARNING will exit with a code of 0 (unless using strict mode for WARNING). ERROR will exit with a non-zero exit code
  • self.compatibility: The list of objects this rule is compatible with. This is used to create separate instances of the Rule for each object in the Rules collection.
  • self.settings: In general, this should default to what is shown here, but allows for overrides
  • self.fn: The function doing the actual work to check the object and enforce the standard.

fn can be as simple or as complex as it needs to be to run a check on a single object. This linter currently does not support Rules that check against multiple objects at a time OR file level formatting (one empty between each step or two empty lines between each job)

To activate a rule after implementing it, add it to settings.yaml in the project's base folder and src/github_actions_workflow_linter/default_settings.yaml to make the rule default

To-Do

  • Remove ActionLint command (should not be wrapped by gawl)
  • Add capability to expose lint level in settings.yaml for Rule Model
  • Add Rule to assert correct format for single line run

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors