Skip to content

Commit

Permalink
feat(config): support for rules & ignore sections in config
Browse files Browse the repository at this point in the history
config file now supports separate sections for ignore directives and auto-merge rules

BREAKING CHANGE:
- config file is now required
- action inputs no longer accept target & command
- config file format has changed
  • Loading branch information
Ahmad Nassri committed Dec 15, 2020
1 parent 67a38ec commit a3557c6
Show file tree
Hide file tree
Showing 19 changed files with 311 additions and 310 deletions.
167 changes: 55 additions & 112 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Automatically merge Dependabot PRs when version comparison is within range.

> **Note:** *Dependabot will wait until all your status checks pass before merging. This is a function of Dependabot itself, and not this Action.*
## Usage
Usage
-----

``` yaml
name: auto-merge
Expand All @@ -25,169 +26,111 @@ jobs:
- uses: actions/checkout@v2
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: minor
config: .github/auto-merge.yml
github-token: ${{ secrets.mytoken }}
```
The action will only merge PRs whose checks (CI/CD) pass.
### Examples
Minimal setup:
``` yaml
steps:
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
github-token: ${{ secrets.mytoken }}
```
Only merge if the changed dependency version is a `patch` *(default behavior)*:

``` yaml
steps:
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: patch
github-token: ${{ secrets.mytoken }}
```

Only merge if the changed dependency version is a `minor`:

``` yaml
steps:
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
target: minor
github-token: ${{ secrets.mytoken }}
```

Using a configuration file:

###### `.github/workflows/auto-merge.yml`

``` yaml
steps:
- uses: actions/checkout@v2
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
github-token: ${{ secrets.mytoken }}
```

###### `.github/auto-merge.yml`

``` yaml
- match:
dependency_type: all
update_type: "semver:minor" # includes patch updates!
```
> **Note**: The action will only merge PRs whose checks (CI/CD) pass.
### Inputs
| input | required | default | description |
| -------------- | -------- | ------------------------ | --------------------------------------------------- |
| `github-token` | ✔ | `github.token` | The GitHub token used to merge the pull-request |
| `config` | ✔ | `.github/auto-merge.yml` | Path to configuration file *(relative to root)* |
| `target` | ❌ | `patch` | The version comparison target (major, minor, patch) |
| `command` | ❌ | `merge` | The command to pass to Dependabot |
| `approve` | ❌ | `true` | Auto-approve pull-requests |
| input | required | default | description |
|----------------|----------|--------------------------|-------------------------------------------------|
| `github-token` | ✔ | `github.token` | The GitHub token used to merge the pull-request |
| `config` | ✔ | `.github/auto-merge.yml` | Path to configuration file *(relative to root)* |

### Token Scope
#### Token Scope

The GitHub token is a [Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the following scopes:

- `repo` for private repositories
- `public_repo` for public repositories
- `repo` for private repositories
- `public_repo` for public repositories

The token MUST be created from a user with **`push`** permission to the repository.

> ℹ *see reference for [user owned repos](https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/permission-levels-for-a-user-account-repository) and for [org owned repos](https://docs.github.com/en/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization)*

### Configuration file syntax
### Configuration File

A configuration file is **REQUIRED** to successfully determine the

Using the configuration file *(specified with `config` input)*, you have the option to provide a more fine-grained configuration. The following example configuration file merges
The syntax is loosely based on the [legacy dependaBot v1 config format](https://dependabot.com/docs/config-file/), with `ignore` and `rules` resembling [`ignored_updates`](https://dependabot.com/docs/config-file/#ignored_updates) and [`automerged_updates`](https://dependabot.com/docs/config-file/#automerged_updates) respectively.

- minor updates for `aws-sdk`
- minor development dependency updates
- patch production dependency updates
- minor security-critical production dependency updates
###### Example

<!-- end list -->
minimal configuration:

``` yaml
- match:
dependency_name: aws-sdk
rules:
- dependency_type: all
update_type: semver:minor
```

- match:
dependency_type: development
update_type: semver:minor # includes patch updates!
advanced configuration:

- match:
dependency_type: production
update_type: security:minor # includes patch updates!
``` yaml
# ignore certain dependencies
ignore:
- dependency_name: react-router
- dependency_name: react-*
# auto merge rules
rules:
# rule for specific dependency
- dependency_name: aws-sdk
update_type: semver:minor
- match:
dependency_type: production
update_type: semver:patch
# rule per dependency type
- dependency_type: development
update_type: semver:minor
# rule per update type
- dependency_type: production
update_type: security:minor
```

#### Match Properties
#### Properties

| property | required | supported values |
| ----------------- | -------- | ------------------------------------------ |
|-------------------|----------|--------------------------------------------|
| `dependency_name` | ❌ | full name of dependency, or a regex string |
| `dependency_type` | ❌ | `all`, `production`, `development` |
| `update_type` | ✔ | `all`, `security:*`, `semver:*` |

> **`update_type`** can specify security match or semver match with the syntax: `${type}:${match}`, e.g.
>
> - **security:patch**
>
> - **security:patch**
> SemVer patch update that fixes a known security vulnerability
>
> - **semver:patch**
> SemVer patch update, e.g. \> 1.x && 1.0.1 to 1.0.3
>
> - **semver:minor**
> SemVer minor update, e.g. \> 1.x && 2.1.4 to 2.3.1
>
>
> - **semver:patch**
> SemVer patch update, e.g. &gt; 1.x && 1.0.1 to 1.0.3
>
> - **semver:minor**
> SemVer minor update, e.g. &gt; 1.x && 2.1.4 to 2.3.1
>
> To allow `prereleases`, the corresponding `prepatch`, `preminor` and `premajor` types are also supported

###### Defaults

By default, if no configuration file is present in the repo, the action will assume the following:

``` yaml
- match:
dependency_type: all
update_type: semver:${TARGET}
```

> Where `$TARGET` is the `target` value from the action [Inputs](#inputs)

The syntax is based on the [legacy dependaBot v1 config format](https://dependabot.com/docs/config-file/#automerged_updates).
However, **`in_range` is not supported yet**.

## Exceptions and Edge Cases
Exceptions and Edge Cases
-------------------------

1. Parsing of *version ranges* is not currently supported

<!-- end list -->
<!-- -->

Update stone requirement from ==1.* to ==3.*
requirements: update sphinx-autodoc-typehints requirement from <=1.11.0 to <1.12.0
Update rake requirement from ~> 10.4 to ~> 13.0

1. Parsing of non semver numbering is not currently supported

<!-- end list -->
<!-- -->

Bump actions/cache from v2.0 to v2.1.2
chore(deps): bump docker/build-push-action from v1 to v2

1. Sometimes Dependabot does not include the "from" version, so version comparison logic is impossible:

<!-- end list -->
<!-- -->

Update actions/setup-python requirement to v2.1.4
Update actions/cache requirement to v2.1.2
Expand Down
15 changes: 0 additions & 15 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,6 @@ inputs:
default: .github/auto-merge.yml
required: false

command:
description: The command to pass to Dependabot as a comment
default: merge
required: false

approve:
description: Auto-approve pull-requests
default: 'true'
required: false

target:
description: The version comparison target (major, minor, patch). This is ignored if .github/auto-merge.yml exists
default: patch
required: false

runs:
using: docker
image: docker://ghcr.io/ahmadnassri/action-dependabot-auto-merge:v2
5 changes: 1 addition & 4 deletions action/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ if (!sender || !['dependabot[bot]', 'dependabot-preview[bot]'].includes(sender.l
// parse inputs
const inputs = {
token: core.getInput('github-token', { required: true }),
config: core.getInput('config', { required: false }),
target: core.getInput('target', { required: false }),
command: core.getInput('command', { required: false }),
approve: core.getInput('approve', { required: false })
config: core.getInput('config', { required: false })
}

// error handler
Expand Down
4 changes: 2 additions & 2 deletions action/lib/api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export async function approve (octokit, repo, { number }, body) {
export async function approve (octokit, repo, number, body) {
await octokit.pulls.createReview({
...repo,
pull_number: number,
Expand All @@ -7,7 +7,7 @@ export async function approve (octokit, repo, { number }, body) {
})
}

export async function comment (octokit, repo, { number }, body) {
export async function comment (octokit, repo, number, body) {
await octokit.issues.createComment({
...repo,
issue_number: number,
Expand Down
30 changes: 24 additions & 6 deletions action/lib/config.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
// internals
import fs from 'fs'
import path from 'path'
import Ajv from 'ajv'

// packages
import core from '@actions/core'
import yaml from 'js-yaml'

// require for json loading
import { createRequire } from 'module'

const require = createRequire(import.meta.url)
const schema = require('./config.schema.json')

const ajv = new Ajv()
const validate = ajv.compile(schema)

// default value is passed from workflow
export default function ({ workspace, inputs }) {
const configPath = path.join(workspace || '', inputs.config || '.github/auto-merge.yml')
export default function ({ workspace = '', filename = '.github/auto-merge.yml' }) {
const configPath = path.join(workspace, filename)

// read auto-merge.yml to determine what should be merged
if (fs.existsSync(configPath)) {
// parse .github/auto-merge.yml
const configYaml = fs.readFileSync(configPath, 'utf8')
const config = yaml.safeLoad(configYaml)

core.info('loaded merge config: \n' + configYaml)

// validate config schema
const valid = validate(config)

if (!valid) {
core.error('invalid config file format')
return process.exit(1)
}

return config
}

// or convert the input "target" to the equivalent config
const config = [{ match: { dependency_type: 'all', update_type: `semver:${inputs.target}` } }]
core.info('using workflow\'s "target": \n' + yaml.safeDump(config))
// create default config structure

return config
core.error('missing config file')
return process.exit(1)
}
62 changes: 62 additions & 0 deletions action/lib/config.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",

"definitions": {
"update_type": {
"type": "string",
"enum": ["security:patch", "semver:patch", "semver:minor", "all"],
"default": "semver:patch"
},

"dependency_type": {
"type": "string",
"enum": ["development", "production", "all"],
"default": "all"
},

"dependency_name": {
"type": "string"
},

"match": {
"type": "object",
"properties": {
"dependency_name": { "$ref": "#/definitions/dependency_name" },
"dependency_type": { "$ref": "#/definitions/dependency_type" },
"update_type": { "$ref": "#/definitions/update_type" }
}
}
},

"type": "object",
"properties": {
"command": {
"description": "The command to pass to Dependabot as a comment",
"type": "string",
"enum": ["merge", "squash and merge"],
"default": "merge"
},

"approve": {
"description": "Auto-approve pull-requests",
"type": "boolean",
"default": true
},

"rules": {
"type": "array",
"default": [],
"items": { "$ref": "#/definitions/match" }
},
"ignore": {
"type": "array",
"default": [],
"items": {
"type": "object",
"properties": {
"dependency_name": { "$ref": "#/definitions/dependency_name" }
}
}
}
}
}
Loading

0 comments on commit a3557c6

Please sign in to comment.