Skip to content

Commit

Permalink
Merge pull request #32 from planningcenter/ns/workflowoutput
Browse files Browse the repository at this point in the history
Replace check runs with workflow output commands
  • Loading branch information
sheck authored Mar 9, 2023
2 parents 7bb7c42 + a45778a commit fe47793
Show file tree
Hide file tree
Showing 15 changed files with 254 additions and 138 deletions.
17 changes: 3 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,31 @@ name: Balto
on: [pull_request]

jobs:
lint:
rubocop:
runs-on: ubuntu-latest
permissions: # may not be necessary, see note below
contents: read
checks: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: ruby/setup-ruby@v1
- uses: planningcenter/balto-rubocop@v1
- uses: planningcenter/balto-rubocop@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
conclusionLevel: "neutral"
```
## Inputs
| Name | Description | Required | Default |
|:-:|-|:-:|:-:|
| `rootDirectory` | The root directory to use for running the action | no | `"."` |
| `conclusionLevel` | Which check run conclusion type to use when annotations are created (`"neutral"` or `"failure"` are most common). See [GitHub Checks documentation](https://developer.github.com/v3/checks/runs/#parameters) for all available options. | no | `"neutral"` |
| `conclusionLevel` | Which workflow status should be used when annotations are created. Currently, `"failure"` and `"action_required"` show as failures, while everything else (including `"neutral"`) show as successful | no | `"neutral"` |
| `additionalGems` | Comma-separated list of other gems that your RuboCop setup depends on, in addition to gems starting with "rubocop", which are installed by default. | no | `""` |
## Outputs

| Name | Description |
|:-:|:-:|
| `issuesCount` | Number of Rubocop violations found |

## A note about permissions

Because some tools, like [dependabot](https://github.com/dependabot), use tokens for actions that have read-only permissions, you'll need to elevate its permissions for this action to work with those sorts of tools. If you don't use any of those tools, and your workflow will only run when users with permissions in your repo create and update pull requests, you may not need these explicit permissions at all.

When defining any permissions in a workflow or job, you need to explicitly include any permission the action needs. In the sample config above, we explicitly give `write` permissons to the [checks API](https://docs.github.com/en/rest/checks/runs) for the job that includes balto-rubocop as a step. Because balto-rubocop uses [check runs](https://docs.github.com/en/rest/guides/getting-started-with-the-checks-api), the `GITHUB_TOKEN` used in an action must have permissions to create a `check run`. You'll also need `contents: read` for `actions/checkout` to be able to clone the code.

## Contributing

### Local testing
Expand Down
75 changes: 40 additions & 35 deletions action/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,29 @@

require_relative "./action_utils"
require_relative "./git_utils"
require_relative "./check_run"

if ENV["BALTO_LOCAL_TEST"]
require_relative "./fake_check_run"
end

CHECK_NAME = "RuboCop"

event = JSON.parse(
File.read(ENV["GITHUB_EVENT_PATH"]),
object_class: OpenStruct
)

check_run_class = ENV["BALTO_LOCAL_TEST"] ? FakeCheckRun : CheckRun

check_run = check_run_class.new(
name: CHECK_NAME,
owner: event.repository.owner.login,
repo: event.repository.name,
token: ENV["GITHUB_TOKEN"],
)

check_run_create = check_run.create(event: event)

if !check_run_create.ok?
raise "Couldn't create check run #{check_run_create.inspect}"
end

RUBOCOP_TO_GITHUB_SEVERITY = {
"refactor" => "warning",
"convention" => "warning",
"warning" => "warning",
"error" => "failure",
"fatal" => "failure"
"error" => "error",
"fatal" => "error"
}.freeze

FAILURE_LEVEL_ANNOTATIONS = RUBOCOP_TO_GITHUB_SEVERITY.select { |_, v| v == "failure" }.keys
FAILURE_LEVEL_ANNOTATIONS = RUBOCOP_TO_GITHUB_SEVERITY.select { |_, v| v == "error" }.keys

CONCLUSION_LEVEL_TO_EXIT_CODE = Hash.new(0).merge({
# When Github finally (re)adds support for setting workflow status to neutral,
# we should change this.
"neutral" => 0,
"failure" => 1,
"action_required" => 1,
})

event = JSON.parse(
File.read(ENV["GITHUB_EVENT_PATH"]),
object_class: OpenStruct
)

def git_root
@git_root ||= Pathname.new(GitUtils.root)
Expand Down Expand Up @@ -77,19 +63,37 @@ def generate_annotations(compare_sha:)
file.offenses.each do |offense|
next unless report_offense?(offense, change_ranges: change_ranges)

annotations.push(
annotation = Annotation.new(
path: path,
start_line: offense.location.start_line,
end_line: offense.location.last_line,
annotation_level: RUBOCOP_TO_GITHUB_SEVERITY[offense.severity],
message: offense.message
)
annotations.push annotation
end
end

annotations
end

def maybe_exit_with_failure(number_of_annotations)
exit_code = CONCLUSION_LEVEL_TO_EXIT_CODE[ENV["INPUT_CONCLUSIONLEVEL"]]
if number_of_annotations > 0 && exit_code > 0
abort "Failing because #{number_of_annotations} annotation(s) found & conclusionLevel is set to #{ENV["INPUT_CONCLUSIONLEVEL"]}"
end
end

Annotation = Struct.new(:path, :start_line, :end_line, :annotation_level, :message, keyword_init: true) do
def to_output_command
args = []
args << "file=#{path}" if path
args << "line=#{start_line}" if start_line
args << "endLine=#{end_line}" if end_line
"::#{annotation_level} #{args.join(',')}::#{message}"
end
end

def report_offense?(offense, change_ranges:)
FAILURE_LEVEL_ANNOTATIONS.include?(offense.severity) ||
change_ranges.any? { |range| range.include?(offense.location.start_line) }
Expand All @@ -115,10 +119,11 @@ def report_offense?(offense, change_ranges:)
rescue Exception => e
puts e.message
puts e.backtrace.inspect
resp = check_run.error(message: e.message)
p resp
p resp.json
abort("::error:: #{e.message}")
else
check_run.update(annotations: annotations)
ActionUtils.set_output("issuesCount", annotations.count)
annotations.each do |note|
puts note.to_output_command
end
maybe_exit_with_failure(annotations.count)
end
31 changes: 0 additions & 31 deletions action/fake_check_run.rb

This file was deleted.

10 changes: 8 additions & 2 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ async function run() {
customEnv.BUNDLE_APP_CONFIG = '/dev/null'

await exec.exec(bundle, ['install'], { env: customEnv })
await exec.exec(
const { exitCode, stdout, stderr } = await exec.getExecOutput(
bundle,
['exec', `${__dirname}/action/action.rb`],
{ env: customEnv }
{ env: customEnv, ignoreReturnCode: true }
)
if (exitCode > 0) {
core.debug(`exit code: ${exitCode}`)
core.debug(`stdout: ${stdout}`)
core.debug(`stderr: ${stderr}`)
core.setFailed(stderr)
}
} catch (e) {
core.setFailed(e.message)
} finally {
Expand Down
2 changes: 2 additions & 0 deletions node_modules/@actions/exec/LICENSE.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 3 additions & 6 deletions node_modules/@actions/exec/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions node_modules/@actions/exec/lib/exec.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 67 additions & 1 deletion node_modules/@actions/exec/lib/exec.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion node_modules/@actions/exec/lib/exec.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fe47793

Please sign in to comment.