Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Lint actions #366

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
61fecfd
Duplicate linting infrastructure to lint actions, too
msw-kialo Oct 9, 2023
dae9310
Draft action metadata parsing
lg-kialo Oct 9, 2023
8992b49
Reenable a basic visitor and rules for action linting
msw-kialo Oct 9, 2023
6b3655c
Add action input parsing
lg-kialo Oct 9, 2023
5328f81
Start parsing actions
msw-kialo Oct 9, 2023
bf2f39c
Resolve staticcheck linting errors
msw-kialo Oct 10, 2023
cde5d87
Parse javascript and docker actions
msw-kialo Oct 10, 2023
f0c006c
Mimic dummy workflow/job while visits actions
msw-kialo Oct 10, 2023
f4693ce
Parse outputs
msw-kialo Oct 10, 2023
88bbdc2
Add support for action linting to playground
lg-kialo Oct 10, 2023
ba8491f
Try to ignore broken test data for dogfooding tests
msw-kialo Oct 10, 2023
8fe56c1
Fix more linting errors
msw-kialo Oct 10, 2023
9f2987a
CI: workaround shellcheck/actionlint limitation
msw-kialo Oct 10, 2023
af49077
Fix playground TS linting errors
lg-kialo Oct 10, 2023
bb3a340
Merge branch 'rhysd:main' into lint-actions
lg-kialo Oct 10, 2023
8c3038a
Add action specific methods to visit interface
msw-kialo Oct 10, 2023
6cc4130
Parse action's branding info and validate it
msw-kialo Oct 11, 2023
bb1e69d
Link to right docs in comment
lg-kialo Oct 11, 2023
ef14959
Address TODO renaming `workflowKeyVal`
lg-kialo Oct 11, 2023
1275407
Update comment
lg-kialo Oct 11, 2023
b95f955
Reverse ActionCommon / Action
msw-kialo Oct 11, 2023
3bd8075
Unify interfaces
msw-kialo Oct 13, 2023
4bf6973
Cleanups and improved naming
msw-kialo Oct 16, 2023
ae2dc96
More fixes
msw-kialo Oct 16, 2023
847623f
Restructure / fix tests
msw-kialo Oct 16, 2023
4b05130
Add more tests
msw-kialo Oct 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
- name: Dog fooding 🐶
run: |
echo "::add-matcher::.github/actionlint-matcher.json"
./actionlint -color
./actionlint -color -input-format=workflow
- uses: codecov/codecov-action@v3
with:
env_vars: OS
Expand Down Expand Up @@ -116,6 +116,6 @@ jobs:
run: docker container run
--mount type=bind,source="$(pwd)",target=/mnt/app
--workdir /mnt/app
-- ${{ steps.image.outputs.digest }} -color -verbose
-- ${{ steps.image.outputs.digest }} -color -verbose -input-format=workflow
- name: Lint Dockerfile with hadolint
run: docker run --rm -i hadolint/hadolint hadolint --ignore DL3018 --strict-labels - < Dockerfile
132 changes: 129 additions & 3 deletions ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,9 @@ func (e *ExecRun) Kind() ExecKind {
return ExecKindRun
}

// Input is an input field for running an action.
// StepInput is an input field for running an action.
// https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswith
type Input struct {
type StepInput struct {
// Name is a name of the input.
Name *String
// Value is a value of the input.
Expand All @@ -437,7 +437,7 @@ type ExecAction struct {
// https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsuses
Uses *String
// Inputs represents inputs to the action to execute in 'with' section. Keys are in lower case since they are case-insensitive.
Inputs map[string]*Input
Inputs map[string]*StepInput
// Entrypoint represents optional 'entrypoint' field in 'with' section. Nil field means nothing specified
// https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswithentrypoint
Entrypoint *String
Expand Down Expand Up @@ -897,6 +897,132 @@ type Workflow struct {
Jobs map[string]*Job
}

// ActionInput is an input field to define the parameters that an action accepts.
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
// TODO This kinda duplicates `ActionMetadataInput` from `action_metadata.go`. Merge them?
type ActionInput struct {
// Description is the description of the input.
Description *String
// Required is a flag to show if the input is required or not.
Required *Bool
// Default is a default value of the input. It can be nil when no default value is specified.
Default *String
// DeprecationMessage is a message to show when the input is deprecated.
DeprecationMessage *String
// Pos is a position in source.
Pos *Pos
}

// ActionOutput is an output field to that is set by this action.
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-docker-container-and-javascript-actions
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-composite-actions
type ActionOutput struct {
// Description is the description of the input.
Description *String
// Value contains the expression that defions the value; for composite action only (nil otherwise)
Value *String
// Pos is a position in source.
Pos *Pos
}

// Branding defines what badges to be shown in GitHub Marketplace for actions.
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#branding
type Branding struct {
// Color defines the background color of the badge.
Color *String
// Icon to use
Icon *String
}

// ActionKind is kind of how the action is executed (JavaScript, Docker or Composite)
type ActionKind uint8

const (
// ActionKindJavascript is kind for actions implemented as node.js scripts
ActionKindJavascript ActionKind = iota
// ActionKindDockerContainer is kind for actions implemented as docker images
ActionKindDockerContainer
// ActionKindComposite is kind for actions implemented as composite actions (sequence of steps)
ActionKindComposite
)

// ActionRuns is an interface how the action is executed. Action can be executed as JavaScript, Docker or composite steps.
type ActionRuns interface {
Comment on lines +949 to +950
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ActionRuns is an interface how the action is executed. Action can be executed as JavaScript, Docker or composite steps.
type ActionRuns interface {
// ActionRuns is an interface how the action is executed. Action can be executed as JavaScript, Docker or composite steps.
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs
type ActionRuns interface {

// Kind returns kind of the Action run
Kind() ActionKind
}

// DockerContainerRuns defines how to run an action implemented as docker container
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-docker-container-actions
type DockerContainerRuns struct {
// Image specifies the container image to use
Image *String
// PreEntrypoint specifices an entrypoint to run before the main script
PreEntrypoint *String
// Entrypoint specifices an entrypoint to run as main action
Entrypoint *String
// Entrypoint specifices an entrypoint to run as main action
Args []*String
// Inputs is a mapping of environment variables to pass to the container
Env *Env
// PostEntrypoint specifices an entrypoint to run at the end of the job
PostEntrypoint *String
}

func (e *DockerContainerRuns) Kind() ActionKind {
return ActionKind(ActionKindDockerContainer)
}

type JavaScriptRuns struct {
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions
// Using specifies the specific node runtime (node18, node20 ..)
Using *String
// Main specifies the entrypoint of the action.
Main *String
// Pre specifices an entrypoint to run before the main script
Pre *String
// PreIf defines an expression whether the pre script should be run
PreIf *String
// Post specifices an entrypoint to run at the end of the job
Post *String
// PostIf specifies an expression whether the post script should be run
PostIf *String
}

func (e *JavaScriptRuns) Kind() ActionKind {
return ActionKind(ActionKindJavascript)
}

// CompositeRuns is configuration how to run composite action at the step.
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions
type CompositeRuns struct {
// Steps specifies the steps to run (as in workflow job steps)
Steps []*Step
}

func (e *CompositeRuns) Kind() ActionKind {
return ActionKind(ActionKindComposite)
}

// Action is root of action syntax tree, which represents one action metadata file.
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
type Action struct {
// Name is the name of the action.
Name *String
// Author is name of the action's author. This field can be nil when user didn't specify it.
Author *String
// Description is the description of the action.
Description *String
// Inputs is a mapping from the input ID to input attributes . This field can be nil when user didn't specify it.
Inputs map[string]*ActionInput
// Outputs is list of outputs of the action. This field can be nil when user didn't specify it.
Outputs map[string]*ActionOutput
// Branding defines what badges to be shown in GitHub Marketplace.
Branding *Branding
// Runs specifies how the action is executed (via JavaScript, Docker or composite steps)
Runs ActionRuns
}

// FindWorkflowCallEvent returns workflow_call event node if exists
func (w *Workflow) FindWorkflowCallEvent() (*WorkflowCallEvent, bool) {
for _, e := range w.On {
Expand Down
15 changes: 15 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"io"
"os"
"runtime"
"runtime/debug"
)
Expand Down Expand Up @@ -77,6 +78,15 @@ type Command struct {
Stderr io.Writer
}

func isDir(path string) bool {
// use a switch to make it a bit cleaner
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// use a switch to make it a bit cleaner
// TODO: use a switch to make it a bit cleaner

todo or remove? (leaving suggestions for both; you pick? 😄)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// use a switch to make it a bit cleaner

fi, err := os.Stat(path)
if err != nil {
return false
}
return fi.IsDir()
}

func (cmd *Command) runLinter(args []string, opts *LinterOptions, initConfig bool) ([]*Error, error) {
l, err := NewLinter(cmd.Stdout, opts)
if err != nil {
Expand All @@ -91,6 +101,10 @@ func (cmd *Command) runLinter(args []string, opts *LinterOptions, initConfig boo
return l.LintRepository(".")
}

if len(args) == 1 && isDir(args[0]) {
return l.LintDirInRepository(args[0])
}

if len(args) == 1 && args[0] == "-" {
b, err := io.ReadAll(cmd.Stdin)
if err != nil {
Expand Down Expand Up @@ -142,6 +156,7 @@ func (cmd *Command) Main(args []string) int {
flags.BoolVar(&opts.Debug, "debug", false, "Enable debug output (for development)")
flags.BoolVar(&ver, "version", false, "Show version and how this binary was installed")
flags.StringVar(&opts.StdinFileName, "stdin-filename", "", "File name when reading input from stdin")
flags.StringVar(&opts.InputFormat, "input-format", "auto-detect", "What syntax to check 'workflow', 'action' or 'auto-detect'")
flags.Usage = func() {
fmt.Fprintln(cmd.Stderr, commandUsageHeader)
flags.PrintDefaults()
Expand Down
Loading