Skip to content

Commit

Permalink
Merge branch 'deprecated-commands' (fix #234)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhysd committed Oct 26, 2022
2 parents a6edfdd + 8edc27f commit 43f6ff2
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 8 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/generate.yaml
Expand Up @@ -29,10 +29,10 @@ jobs:
- run: go generate
- run: |
if git diff-files --quiet; then
echo '::set-output name=pr::false'
echo "pr=false" >> "$GITHUB_OUTPUT"
else
git diff
echo '::set-output name=pr::true'
echo "pr=true" >> "$GITHUB_OUTPUT"
fi
id: diff
- uses: peter-evans/create-pull-request@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Expand Up @@ -49,7 +49,7 @@ jobs:
- name: Get tag name
id: tag
run: |
echo "::set-output name=name::${GITHUB_REF#refs/tags/v}"
echo "name=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
Expand Down
47 changes: 46 additions & 1 deletion docs/checks.md
Expand Up @@ -36,6 +36,7 @@ List of checks:
- [Reusable workflows](#check-reusable-workflows)
- [ID naming convention](#id-naming-convention)
- [Contexts and special functions availability](#ctx-spfunc-availability)
- [Deprecated workflow commands](#check-deprecated-workflow-commands)

Note that actionlint focuses on catching mistakes in workflow files. If you want some general code style checks, please consider
using a general YAML checker like [yamllint][].
Expand Down Expand Up @@ -443,7 +444,7 @@ jobs:
# ERROR: Access undefined step outputs
- run: echo '${{ steps.get_value.outputs.name }}'
# Outputs are set here
- run: echo '::set-output name=foo::value'
- run: echo "foo=value" >> "$GITHUB_OUTPUT"
id: get_value
# OK
- run: echo '${{ steps.get_value.outputs.name }}'
Expand Down Expand Up @@ -2476,6 +2477,47 @@ keys.
actionlint checks if these contexts and special functions are used correctly. It reports an error when it finds that some context
or special function is not available in your workflow.

<a name="#check-deprecated-workflow-commands"></a>
## Check deprecated workflow commands

Example input:

```yaml
on: push

jobs:
test:
runs-on: ubuntu-latest
steps:
# ERROR: 'set-output' workflow command was deprecated
- run: echo '::set-output name=foo::bar'
# OK: Use this instead
- run: echo "foo=bar" >> "$GITHUB_OUTPUT"
# OK: 'debug' command is not deprecated
- run: echo "::debug::Set the Octocat variable"
```

Output:

```
test.yaml:8:14: workflow command "set-output" was deprecated. use `echo "{name}={value}" >> $GITHUB_OUTPUT` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions [deprecated-commands]
|
8 | - run: echo '::set-output name=foo::bar'
| ^~~~
```

[Playground](https://rhysd.github.io/actionlint#eJxtyjEOgkAQRuGeU/zZmFDtBSaBwkatMBFqs4ujaHCHsDOeX9CW6hXvk0SYLA9F8ZKYqQCUs64FZkvZywIsWlLzY1jfb2XlKf8V4FdJ4H4QlESZ1YvpZIoU3lzdRYhimMsN7pZZLc+hruF2h1N77PbXpmvPXeu2PNGNoz2ILqzQgdH0Kn1QfML8DHFk9wXMjT7o)

GitHub deprecated the following workflow commands.

- [`set-output`][deprecate-set-output-save-state]
- [`save-state`][deprecate-set-output-save-state]
- [`set-env`][deprecate-set-env-add-path]
- [`add-path`][deprecate-set-env-add-path]

actionlint detects these commands are used in `run:` and reports them as errors suggesting alternatives. See
[the official document][workflow-commands-doc] for the comprehensive list of workflow commands to know the usage.

---

[Installation](install.md) | [Usage](usage.md) | [Configuration](config.md) | [Go API](api.md) | [References](reference.md)
Expand Down Expand Up @@ -2525,3 +2567,6 @@ or special function is not available in your workflow.
[inherit-secrets-announce]: https://github.blog/changelog/2022-05-03-github-actions-simplify-using-secrets-with-reusable-workflows/
[specific-paths-doc]: https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#using-filters-to-target-specific-paths-for-pull-request-or-push-events
[availability-doc]: https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
[deprecate-set-output-save-state]: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
[deprecate-set-env-add-path]: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/
[workflow-commands-doc]: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
1 change: 1 addition & 0 deletions linter.go
Expand Up @@ -502,6 +502,7 @@ func (l *Linter) check(
NewRulePermissions(),
NewRuleWorkflowCall(path, localReusableWorkflows),
NewRuleExpression(localActions, localReusableWorkflows),
NewRuleDeprecatedCommands(),
}
if l.shellcheck != "" {
r, err := NewRuleShellcheck(l.shellcheck, proc)
Expand Down
55 changes: 55 additions & 0 deletions rule_deprecated_commands.go
@@ -0,0 +1,55 @@
package actionlint

import "regexp"

var deprecatedCommandsPattern = regexp.MustCompile(`(?:::(save-state|set-output|set-env)\s+name=[a-zA-Z][a-zA-Z_-]*::\S+|::(add-path)::\S+)`)

// RuleDeprecatedCommands is a rule checker to detect deprecated workflow commands. Currently
// 'set-state', 'set-output', `set-env' and 'add-path' are detected as deprecated.
//
// - https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/
// - https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
type RuleDeprecatedCommands struct {
RuleBase
}

// NewRuleDeprecatedCommands creates a new RuleDeprecatedCommands instance.
func NewRuleDeprecatedCommands() *RuleDeprecatedCommands {
return &RuleDeprecatedCommands{
RuleBase: RuleBase{name: "deprecated-commands"},
}
}

// VisitStep is callback when visiting Step node.
func (rule *RuleDeprecatedCommands) VisitStep(n *Step) error {
if r, ok := n.Exec.(*ExecRun); ok && r.Run != nil {
for _, m := range deprecatedCommandsPattern.FindAllStringSubmatch(r.Run.Value, -1) {
c := m[1]
if len(c) == 0 {
c = m[2]
}

var a string
switch c {
case "set-output":
a = `echo "{name}={value}" >> $GITHUB_OUTPUT`
case "save-state":
a = `echo "{name}={value}" >> $GITHUB_STATE`
case "set-env":
a = `echo "{name}={value}" >> $GITHUB_ENV`
case "add-path":
a = `echo "{path}" >> $GITHUB_PATH`
default:
panic("unreachable")
}

rule.errorf(
r.Run.Pos,
"workflow command %q was deprecated. use `%s` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions",
c,
a,
)
}
}
return nil
}
125 changes: 125 additions & 0 deletions rule_deprecated_commands_test.go
@@ -0,0 +1,125 @@
package actionlint

import (
"regexp"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestRuleDeprecatedCommandsDetectTargetCommands(t *testing.T) {
tests := []struct {
what string
run string
want []string
}{
{
what: "save-state",
run: "::save-state name=foo::42",
want: []string{"save-state"},
},
{
what: "set-output",
run: "::set-output name=foo::42",
want: []string{"set-output"},
},
{
what: "set-env",
run: "::set-env name=foo::42",
want: []string{"set-env"},
},
{
what: "add-path",
run: "::add-path::/path/to/foo",
want: []string{"add-path"},
},
{
what: "submatch",
run: "hello::set-output name=foo::42 world",
want: []string{"set-output"},
},
{
what: "multiple same commands",
run: "::set-output name=foo::42 ::set-output name=bar::xxx",
want: []string{"set-output", "set-output"},
},
{
what: "multiple different commands",
run: "::set-output name=foo::42 ::add-path::/path/to/foo ::save-state name=foo::42",
want: []string{"set-output", "add-path", "save-state"},
},
{
what: "multiple submatches",
run: "hello::set-output name=foo::42 how ::add-path::/path/to/foo are ::save-state name=foo::42 you",
want: []string{"set-output", "add-path", "save-state"},
},
{
what: "something between command and arguments",
run: "::set-output hello name=foo::42",
want: []string{},
},
{
what: "multiple spaces",
run: "::set-output name=foo::42",
want: []string{"set-output"},
},
{
what: "empty string",
run: "",
want: []string{},
},
{
what: "no command",
run: "echo 'do not use set-output!'",
want: []string{},
},
{
what: "hyphen and underscore in name",
run: "::save-state name=foo_bar-woo::42",
want: []string{"save-state"},
},
{
what: "invalid name",
run: "::save-state name=-foo::42",
want: []string{},
},
{
what: "different argument",
run: "::save-state myname=foo::42",
want: []string{},
},
}
re := regexp.MustCompile(`\s+workflow command "([a-z-]+)" was deprecated\.`)

for _, tc := range tests {
t.Run(tc.what, func(t *testing.T) {
s := &Step{
Exec: &ExecRun{
Run: &String{
Value: tc.run,
Pos: &Pos{},
},
},
}
r := NewRuleDeprecatedCommands()
if err := r.VisitStep(s); err != nil {
t.Fatal(err)
}

errs := r.Errs()
have := []string{}
for i, err := range errs {
m := err.Error()
ss := re.FindStringSubmatch(m)
if len(ss) == 0 {
t.Fatalf("%dth error was unexpected: %q", i, m)
}
have = append(have, ss[1])
}

if !cmp.Equal(have, tc.want) {
t.Fatal(cmp.Diff(have, tc.want))
}
})
}
}
2 changes: 1 addition & 1 deletion scripts/download-actionlint.bash
Expand Up @@ -132,5 +132,5 @@ echo "Done: $("${exe}" -version)"

if [ -n "$GITHUB_ACTION" ]; then
# On GitHub Actions, set executable path to output
echo "::set-output name=executable::${exe}"
echo "executable=${exe}" >> "$GITHUB_OUTPUT"
fi
2 changes: 1 addition & 1 deletion scripts/test-download-actionlint.bash
Expand Up @@ -21,7 +21,7 @@ set -e
# No arguments
out="$(bash "$script")"
if [ -n "$GITHUB_ACTION" ]; then
if [[ "$out" != *"::set-output name=executable::"* ]]; then
if [[ "$out" != *"executable="* ]]; then
echo "'executable' step output is not set: '${out}'" >&2
fi
fi
Expand Down
4 changes: 4 additions & 0 deletions testdata/err/deprecated_workflow_commands.out
@@ -0,0 +1,4 @@
test.yaml:8:14: workflow command "set-output" was deprecated. use `echo "{name}={value}" >> $GITHUB_OUTPUT` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions [deprecated-commands]
test.yaml:9:14: workflow command "save-state" was deprecated. use `echo "{name}={value}" >> $GITHUB_STATE` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions [deprecated-commands]
test.yaml:10:14: workflow command "set-env" was deprecated. use `echo "{name}={value}" >> $GITHUB_ENV` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions [deprecated-commands]
test.yaml:11:14: workflow command "add-path" was deprecated. use `echo "{path}" >> $GITHUB_PATH` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions [deprecated-commands]
20 changes: 20 additions & 0 deletions testdata/err/deprecated_workflow_commands.yaml
@@ -0,0 +1,20 @@
on: push

jobs:
test:
runs-on: ubuntu-latest
steps:
# ERROR
- run: echo '::set-output name=foo::bar'
- run: echo '::save-state name=foo::bar'
- run: echo '::set-env name=foo::bar'
- run: echo '::add-path::/path/to/foo'
# OK
- run: echo "::debug::Set the Octocat variable"
- run: echo "::notice file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
- run: echo "::warning file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
- run: echo "::error file=app.js,line=1,col=5,endColumn=7::Missing semicolon"
- run: echo "::group::My title"
- run: echo "::endgroup::"
- run: echo "::add-mask::Mona The Octocat"
- run: echo "::stop-commands::hogehoge"
2 changes: 1 addition & 1 deletion testdata/examples/contextual_steps_outputs.yaml
Expand Up @@ -9,7 +9,7 @@ jobs:
# Access undefined step outputs
- run: echo '${{ steps.get_value.outputs.name }}'
# Outputs are set here
- run: echo '::set-output name=foo::value'
- run: echo "foo=value" >> "$GITHUB_OUTPUT"
id: get_value
# OK
- run: echo '${{ steps.get_value.outputs.name }}'
Expand Down
1 change: 1 addition & 0 deletions testdata/examples/deprecated_workflow_commands.out
@@ -0,0 +1 @@
test.yaml:8:14: workflow command "set-output" was deprecated. use `echo "{name}={value}" >> $GITHUB_OUTPUT` instead: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions [deprecated-commands]
12 changes: 12 additions & 0 deletions testdata/examples/deprecated_workflow_commands.yaml
@@ -0,0 +1,12 @@
on: push

jobs:
test:
runs-on: ubuntu-latest
steps:
# ERROR: 'set-output' workflow command was deprecated
- run: echo '::set-output name=foo::bar'
# OK: Use this instead
- run: echo "foo=bar" >> "$GITHUB_OUTPUT"
# OK: 'debug' command is not deprecated
- run: echo "::debug::Set the Octocat variable"
2 changes: 1 addition & 1 deletion testdata/ok/no_description_workflow_call.yaml
Expand Up @@ -18,6 +18,6 @@ jobs:
output1: ${{ steps.step1.outputs.firstword }}
steps:
- id: step1
run: echo "::set-output name=firstword::hello, ${{ inputs.username }}"
run: echo "firstword=hello, ${{ inputs.username }}" >> "$GITHUB_OUTPUT"
env:
TOKEN: ${{ secrets.token }}

0 comments on commit 43f6ff2

Please sign in to comment.