diff options
Diffstat (limited to '.github')
-rw-r--r-- | .github/actions/rn-pr-labeler-action/action.yml | 41 | ||||
-rw-r--r-- | .github/changelog.sh | 435 | ||||
-rw-r--r-- | .github/dependabot.yml | 34 | ||||
-rw-r--r-- | .github/generate_release.py | 117 | ||||
-rw-r--r-- | .github/license-short.txt | 3 | ||||
-rw-r--r-- | .github/pull_request_template.md | 16 | ||||
-rw-r--r-- | .github/release.md | 103 | ||||
-rw-r--r-- | .github/release.yml | 59 | ||||
-rw-r--r-- | .github/workflows/code-testing.yml | 144 | ||||
-rw-r--r-- | .github/workflows/main-doc.yml | 37 | ||||
-rw-r--r-- | .github/workflows/on-demand.yml | 49 | ||||
-rw-r--r-- | .github/workflows/pr-conflicts.yml | 18 | ||||
-rw-r--r-- | .github/workflows/pr-triage.yml | 73 | ||||
-rw-r--r-- | .github/workflows/pull-request-rn-labeler.yml | 27 | ||||
-rw-r--r-- | .github/workflows/release.yml | 110 |
15 files changed, 1266 insertions, 0 deletions
diff --git a/.github/actions/rn-pr-labeler-action/action.yml b/.github/actions/rn-pr-labeler-action/action.yml new file mode 100644 index 0000000..a685ffc --- /dev/null +++ b/.github/actions/rn-pr-labeler-action/action.yml @@ -0,0 +1,41 @@ +name: "rn-pr-labeler" +author: "@gmuloc" +description: "Parse a conventional commit compliant PR title and add it as a label to the PR with the prefix 'rn: '" +inputs: + auto_create_label: + description: "Boolean to indicate if the label should be auto created" + required: false + default: false +runs: + using: "composite" + steps: + - name: 'Looking up existing "rn:" label' + run: | + echo "OLD_LABEL=$(gh pr view ${{ github.event.pull_request.number }} --json labels -q .labels[].name | grep 'rn: ')" >> $GITHUB_ENV + shell: bash + - name: 'Delete existing "rn:" label if found' + run: gh pr edit ${{ github.event.pull_request.number }} --remove-label "${{ env.OLD_LABEL }}" + shell: bash + if: ${{ env.OLD_LABEL }} + - name: Set Label + # Using toJSON to support ' and " in commit messages + # https://stackoverflow.com/questions/73363167/github-actions-how-to-escape-characters-in-commit-message + run: echo "LABEL=$(echo ${{ toJSON(github.event.pull_request.title) }} | cut -d ':' -f 1 | tr -d ' ')" >> $GITHUB_ENV + shell: bash + # an alternative to verifying if the target label already exist is to + # create the label with --force in the next step, it will keep on changing + # the color of the label though so it may not be desirable. + - name: Check if label exist + run: | + EXIST=$(gh label list -L 100 --search "rn:" --json name -q '.[] | select(.name=="rn: ${{ env.LABEL }}").name') + echo "EXIST=$EXIST" >> $GITHUB_ENV + shell: bash + - name: Create Label if auto-create and label does not exist already + run: | + gh label create "rn: ${{ env.LABEL }}" + shell: bash + if: ${{ inputs.auto_create_label && ! env.EXIST }} + - name: Labelling PR + run: | + gh pr edit ${{ github.event.pull_request.number }} --add-label "rn: ${{ env.LABEL }}" + shell: bash diff --git a/.github/changelog.sh b/.github/changelog.sh new file mode 100644 index 0000000..2d43826 --- /dev/null +++ b/.github/changelog.sh @@ -0,0 +1,435 @@ +#!/usr/bin/env zsh + +############################## +# CHANGELOG SCRIPT CONSTANTS # +############################## + +#* Holds the list of valid types recognized in a commit subject +#* and the display string of such type +local -A TYPES +TYPES=( + BUILD "Build system" + CHORE "Chore" + CI "CI" + CUT "Features removed" + DOC "Documentation" + FEAT "Features" + FIX "Bug fixes" + LICENSE "License update" + MAKE "Build system" + OPTIMIZE "Code optimization" + PERF "Performance" + REFACTOR "Code Refactoring" + REFORMAT "Code Reformating" + REVERT "Revert" + TEST "Testing" +) + +#* Types that will be displayed in their own section, +#* in the order specified here. +local -a MAIN_TYPES +MAIN_TYPES=(FEAT FIX PERF REFACTOR DOCS DOC) + +#* Types that will be displayed under the category of other changes +local -a OTHER_TYPES +OTHER_TYPES=(MAKE TEST STYLE CI OTHER) + +#* Commit types that don't appear in $MAIN_TYPES nor $OTHER_TYPES +#* will not be displayed and will simply be ignored. + + +############################ +# COMMIT PARSING UTILITIES # +############################ + +function parse-commit { + + # This function uses the following globals as output: commits (A), + # subjects (A), scopes (A) and breaking (A). All associative arrays (A) + # have $hash as the key. + # - commits holds the commit type + # - subjects holds the commit subject + # - scopes holds the scope of a commit + # - breaking holds the breaking change warning if a commit does + # make a breaking change + + function commit:type { + local commit_message="$1" + local type="$(sed -E 's/^([a-zA-Z_\-]+)(\(.+\))?!?: .+$/\1/' <<< "$commit_message"| tr '[:lower:]' '[:upper:]')" + # If $type doesn't appear in $TYPES array mark it as 'other' + if [[ -n "${(k)TYPES[(i)${type}]}" ]]; then + echo $type + else + echo other + fi + } + + function commit:scope { + local scope + + # Try to find scope in "type(<scope>):" format + # Scope will be formatted in lower cases + scope=$(sed -nE 's/^[a-zA-Z_\-]+\((.+)\)!?: .+$/\1/p' <<< "$1") + if [[ -n "$scope" ]]; then + echo "$scope" | tr '[:upper:]' '[:lower:]' + return + fi + + # If no scope found, try to find it in "<scope>:" format + # Make sure it's not a type before printing it + scope=$(sed -nE 's/^([a-zA-Z_\-]+): .+$/\1/p' <<< "$1") + if [[ -z "${(k)TYPES[(i)$scope]}" ]]; then + echo "$scope" + fi + } + + function commit:subject { + # Only display the relevant part of the commit, i.e. if it has the format + # type[(scope)!]: subject, where the part between [] is optional, only + # displays subject. If it doesn't match the format, returns the whole string. + sed -E 's/^[a-zA-Z_\-]+(\(.+\))?!?: (.+)$/\2/' <<< "$1" + } + + # Return subject if the body or subject match the breaking change format + function commit:is-breaking { + local subject="$1" body="$2" message + + if [[ "$body" =~ "BREAKING CHANGE: (.*)" || \ + "$subject" =~ '^[^ :\)]+\)?!: (.*)$' ]]; then + message="${match[1]}" + # remove CR characters (might be inserted in GitHub UI commit description form) + message="${message//$'\r'/}" + # skip next paragraphs (separated by two newlines or more) + message="${message%%$'\n\n'*}" + # ... and replace newlines with spaces + echo "${message//$'\n'/ }" + else + return 1 + fi + } + + # Return truncated hash of the reverted commit + function commit:is-revert { + local subject="$1" body="$2" + + if [[ "$subject" = Revert* && \ + "$body" =~ "This reverts commit ([^.]+)\." ]]; then + echo "${match[1]:0:7}" + else + return 1 + fi + } + + # Parse commit with hash $1 + local hash="$1" subject body warning rhash + subject="$(command git show -s --format=%s $hash)" + body="$(command git show -s --format=%b $hash)" + + # Commits following Conventional Commits (https://www.conventionalcommits.org/) + # have the following format, where parts between [] are optional: + # + # type[(scope)][!]: subject + # + # commit body + # [BREAKING CHANGE: warning] + + # commits holds the commit type + commits[$hash]="$(commit:type "$subject")" + # scopes holds the commit scope + scopes[$hash]="$(commit:scope "$subject")" + # subjects holds the commit subject + subjects[$hash]="$(commit:subject "$subject")" + + # breaking holds whether a commit has breaking changes + # and its warning message if it does + if warning=$(commit:is-breaking "$subject" "$body"); then + breaking[$hash]="$warning" + fi + + # reverts holds commits reverted in the same release + if rhash=$(commit:is-revert "$subject" "$body"); then + reverts[$hash]=$rhash + fi +} + +############################# +# RELEASE CHANGELOG DISPLAY # +############################# + +function display-release { + + # This function uses the following globals: output, version, + # commits (A), subjects (A), scopes (A), breaking (A) and reverts (A). + # + # - output is the output format to use when formatting (raw|text|md) + # - version is the version in which the commits are made + # - commits, subjects, scopes, breaking, and reverts are associative arrays + # with commit hashes as keys + + # Remove commits that were reverted + local hash rhash + for hash rhash in ${(kv)reverts}; do + if (( ${+commits[$rhash]} )); then + # Remove revert commit + unset "commits[$hash]" "subjects[$hash]" "scopes[$hash]" "breaking[$hash]" + # Remove reverted commit + unset "commits[$rhash]" "subjects[$rhash]" "scopes[$rhash]" "breaking[$rhash]" + fi + done + + # If no commits left skip displaying the release + if (( $#commits == 0 )); then + return + fi + + ##* Formatting functions + + # Format the hash according to output format + # If no parameter is passed, assume it comes from `$hash` + function fmt:hash { + #* Uses $hash from outer scope + local hash="${1:-$hash}" + case "$output" in + raw) printf "$hash" ;; + text) printf "\e[33m$hash\e[0m" ;; # red + md) printf "[\`$hash\`](https://github.com/aristanetworks/ansible-avd/commit/$hash)" ;; + esac + } + + # Format headers according to output format + # Levels 1 to 2 are considered special, the rest are formatted + # the same, except in md output format. + function fmt:header { + local header="$1" level="$2" + case "$output" in + raw) + case "$level" in + 1) printf "$header\n$(printf '%.0s=' {1..${#header}})\n\n" ;; + 2) printf "$header\n$(printf '%.0s-' {1..${#header}})\n\n" ;; + *) printf "$header:\n\n" ;; + esac ;; + text) + case "$level" in + 1|2) printf "\e[1;4m$header\e[0m\n\n" ;; # bold, underlined + *) printf "\e[1m$header:\e[0m\n\n" ;; # bold + esac ;; + md) printf "$(printf '%.0s#' {1..${level}}) $header\n\n" ;; + esac + } + + function fmt:scope { + #* Uses $scopes (A) and $hash from outer scope + local scope="${1:-${scopes[$hash]}}" + + # Get length of longest scope for padding + local max_scope=0 padding=0 + for hash in ${(k)scopes}; do + max_scope=$(( max_scope < ${#scopes[$hash]} ? ${#scopes[$hash]} : max_scope )) + done + + # If no scopes, exit the function + if [[ $max_scope -eq 0 ]]; then + return + fi + + # Get how much padding is required for this scope + padding=$(( max_scope < ${#scope} ? 0 : max_scope - ${#scope} )) + padding="${(r:$padding:: :):-}" + + # If no scope, print padding and 3 spaces (equivalent to "[] ") + if [[ -z "$scope" ]]; then + printf "${padding} " + return + fi + + # Print [scope] + case "$output" in + raw|md) printf "[$scope]${padding} " ;; + text) printf "[\e[38;5;9m$scope\e[0m]${padding} " ;; # red 9 + esac + } + + # If no parameter is passed, assume it comes from `$subjects[$hash]` + function fmt:subject { + #* Uses $subjects (A) and $hash from outer scope + local subject="${1:-${subjects[$hash]}}" + + # Capitalize first letter of the subject + subject="${(U)subject:0:1}${subject:1}" + + case "$output" in + raw) printf "$subject" ;; + # In text mode, highlight (#<issue>) and dim text between `backticks` + text) sed -E $'s|#([0-9]+)|\e[32m#\\1\e[0m|g;s|`([^`]+)`|`\e[2m\\1\e[0m`|g' <<< "$subject" ;; + # In markdown mode, link to (#<issue>) issues + md) sed -E 's|#([0-9]+)|[#\1](https://github.com/aristanetworks/ansible-avd/issues/\1)|g' <<< "$subject" ;; + esac + } + + function fmt:type { + #* Uses $type from outer scope + local type="${1:-${TYPES[$type]:-${(C)type}}}" + [[ -z "$type" ]] && return 0 + case "$output" in + raw|md) printf "$type: " ;; + text) printf "\e[4m$type\e[24m: " ;; # underlined + esac + } + + ##* Section functions + + function display:version { + fmt:header "$version" 2 + } + + function display:breaking { + (( $#breaking != 0 )) || return 0 + + case "$output" in + raw) fmt:header "BREAKING CHANGES" 3 ;; + text|md) fmt:header "⚠ BREAKING CHANGES" 3 ;; + esac + + local hash subject + for hash message in ${(kv)breaking}; do + echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject "${message}")" + done | sort + echo + } + + function display:type { + local hash type="$1" + + local -a hashes + hashes=(${(k)commits[(R)$type]}) + + # If no commits found of type $type, go to next type + (( $#hashes != 0 )) || return 0 + + fmt:header "${TYPES[$type]}" 3 + for hash in $hashes; do + echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" + done | sort -k3 # sort by scope + echo + } + + function display:others { + local hash type + + # Commits made under types considered other changes + local -A changes + changes=(${(kv)commits[(R)${(j:|:)OTHER_TYPES}]}) + + # If no commits found under "other" types, don't display anything + (( $#changes != 0 )) || return 0 + + fmt:header "Other changes" 3 + for hash type in ${(kv)changes}; do + case "$type" in + other) echo " - $(fmt:hash) $(fmt:scope)$(fmt:subject)" ;; + *) echo " - $(fmt:hash) $(fmt:scope)$(fmt:type)$(fmt:subject)" ;; + esac + done | sort -k3 # sort by scope + echo + } + + ##* Release sections order + + # Display version header + display:version + + # Display breaking changes first + display:breaking + + # Display changes for commit types in the order specified + for type in $MAIN_TYPES; do + display:type "$type" + done + + # Display other changes + display:others +} + +function main { + # $1 = until commit, $2 = since commit + local until="$1" since="$2" + + # $3 = output format (--text|--raw|--md) + # --md: uses markdown formatting + # --raw: outputs without style + # --text: uses ANSI escape codes to style the output + local output=${${3:-"--text"}#--*} + + if [[ -z "$until" ]]; then + until=HEAD + fi + + if [[ -z "$since" ]]; then + # If $since is not specified: + # 1) try to find the version used before updating + # 2) try to find the first version tag before $until + since=$(command git config --get ansible-avd.lastVersion 2>/dev/null) || \ + since=$(command git describe --abbrev=0 --tags "$until^" 2>/dev/null) || \ + unset since + elif [[ "$since" = --all ]]; then + unset since + fi + + # Commit classification arrays + local -A commits subjects scopes breaking reverts + local truncate=0 read_commits=0 + local hash version tag + + # Get the first version name: + # 1) try tag-like version, or + # 2) try name-rev, or + # 3) try branch name, or + # 4) try short hash + version=$(command git describe --tags $until 2>/dev/null) \ + || version=$(command git name-rev --no-undefined --name-only --exclude="remotes/*" $until 2>/dev/null) \ + || version=$(command git symbolic-ref --quiet --short $until 2>/dev/null) \ + || version=$(command git rev-parse --short $until 2>/dev/null) + + # Get commit list from $until commit until $since commit, or until root + # commit if $since is unset, in short hash form. + # --first-parent is used when dealing with merges: it only prints the + # merge commit, not the commits of the merged branch. + command git rev-list --first-parent --abbrev-commit --abbrev=7 ${since:+$since..}$until | while read hash; do + # Truncate list on versions with a lot of commits + if [[ -z "$since" ]] && (( ++read_commits > 35 )); then + truncate=1 + break + fi + + # If we find a new release (exact tag) + if tag=$(command git describe --exact-match --tags $hash 2>/dev/null); then + # Output previous release + display-release + # Reinitialize commit storage + commits=() + subjects=() + scopes=() + breaking=() + reverts=() + # Start work on next release + version="$tag" + read_commits=1 + fi + + parse-commit "$hash" + done + + display-release + + if (( truncate )); then + echo " ...more commits omitted" + echo + fi +} + +# Use raw output if stdout is not a tty +if [[ ! -t 1 && -z "$3" ]]; then + main "$1" "$2" --raw +else + main "$@" +fi diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..30fd0aa --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +# Basic set up for three package managers + +version: 2 +updates: + # Maintain dependencies for Python + # Dependabot supports updates to pyproject.toml files + # if they follow the PEP 621 standard. + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + reviewers: + - "titom73" + - "gmuloc" + - "mtache" + - "carl-baillargeon" + labels: + - 'dependencies' + pull-request-branch-name: + separator: "/" + commit-message: + prefix: "chore: " + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + reviewers: + - "titom73" + - "gmuloc" + labels: + - 'CI' + commit-message: + prefix: "ci: "
\ No newline at end of file diff --git a/.github/generate_release.py b/.github/generate_release.py new file mode 100644 index 0000000..56b6500 --- /dev/null +++ b/.github/generate_release.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +""" +generate_release.py + +This script is used to generate the release.yml file as per +https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes +""" + +import yaml + +SCOPES = [ + "anta", + "anta.tests", + "anta.cli", +] + +# CI and Test are excluded from Release Notes +CATEGORIES = { + "feat": "Features", + "fix": "Bug Fixes", + "cut": "Cut", + "doc": "Documentation", + # "CI": "CI", + "bump": "Bump", + # "test": "Test", + "revert": "Revert", + "refactor": "Refactoring", +} + + +class SafeDumper(yaml.SafeDumper): + """ + Make yamllint happy + https://github.com/yaml/pyyaml/issues/234#issuecomment-765894586 + """ + + # pylint: disable=R0901,W0613,W1113 + + def increase_indent(self, flow=False, *args, **kwargs): + return super().increase_indent(flow=flow, indentless=False) + + +if __name__ == "__main__": + exclude_list = [] + categories_list = [] + + # First add exclude labels + for scope in SCOPES: + exclude_list.append(f"rn: test({scope})") + exclude_list.append(f"rn: ci({scope})") + exclude_list.extend(["rn: test", "rn: ci"]) + + # Then add the categories + # First add Breaking Changes + breaking_label_categories = ["feat", "fix", "cut", "revert", "refactor", "bump"] + breaking_labels = [f"rn: {cc_type}({scope})!" for cc_type in breaking_label_categories for scope in SCOPES] + breaking_labels.extend([f"rn: {cc_type}!" for cc_type in breaking_label_categories]) + + categories_list.append( + { + "title": "Breaking Changes", + "labels": breaking_labels, + } + ) + + # Add new features + feat_labels = [f"rn: feat({scope})" for scope in SCOPES] + feat_labels.append("rn: feat") + + categories_list.append( + { + "title": "New features and enhancements", + "labels": feat_labels, + } + ) + + # Add fixes + fixes_labels = [f"rn: fix({scope})" for scope in SCOPES] + fixes_labels.append("rn: fix") + + categories_list.append( + { + "title": "Fixed issues", + "labels": fixes_labels, + } + ) + + # Add Documentation + doc_labels = [f"rn: doc({scope})" for scope in SCOPES] + doc_labels.append("rn: doc") + + categories_list.append( + { + "title": "Documentation", + "labels": doc_labels, + } + ) + + # Add the catch all + categories_list.append( + { + "title": "Other Changes", + "labels": ["*"], + } + ) + with open(r"release.yml", "w", encoding="utf-8") as release_file: + yaml.dump( + { + "changelog": { + "exclude": {"labels": exclude_list}, + "categories": categories_list, + } + }, + release_file, + Dumper=SafeDumper, + sort_keys=False, + ) diff --git a/.github/license-short.txt b/.github/license-short.txt new file mode 100644 index 0000000..787e7ab --- /dev/null +++ b/.github/license-short.txt @@ -0,0 +1,3 @@ +Copyright (c) 2023 Arista Networks, Inc. +Use of this source code is governed by the Apache License 2.0 +that can be found in the LICENSE file. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..ba76374 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +# Description + +<!-- PR description !--> + +Fixes # (issue id) + +# Checklist: + +<!-- Delete not relevant items !--> + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have run pre-commit for code linting and typing (`pre-commit run`) +- [ ] I have made corresponding changes to the documentation +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes (`tox -e testenv`) diff --git a/.github/release.md b/.github/release.md new file mode 100644 index 0000000..15db226 --- /dev/null +++ b/.github/release.md @@ -0,0 +1,103 @@ +# Notes + +Notes regarding how to release anta package + +## Package requirements + +- `bumpver` +- `build` +- `twine` + +Also, [Github CLI](https://cli.github.com/) can be helpful and is recommended + +## Bumping version + +In a branch specific for this, use the `bumpver` tool. +It is configured to update: +* pyproject.toml +* docs/contribution.md +* docs/requirements-and-installation.md + +For instance to bump a patch version: +``` +bumpver update --patch +``` + +and for a minor version + +``` +bumpver update --minor +``` + +Tip: It is possible to check what the changes would be using `--dry` + +``` +bumpver update --minor --dry +``` + +## Creating release on Github + +Create the release on Github with the appropriate tag `vx.x.x` + +## Release version `x.x.x` + +> [!IMPORTANT] +> TODO - make this a github workflow + +`x.x.x` is the version to be released + +This is to be executed at the top of the repo + +1. Checkout the latest version of `main` with the correct tag for the release +2. Create a new branch for release + + ```bash + git switch -c rel/vx.x.x + ``` +3. [Optional] Clean dist if required +4. Build the package locally + + ```bash + python -m build + ``` +5. Check the package with `twine` (replace with your vesion) + + ```bash + twine check dist/* + ``` +6. Upload the package to test.pypi + + ```bash + twine upload -r testpypi dist/anta-x.x.x.* + ``` +7. Verify the package by installing it in a local venv and checking it installs + and run correctly (run the tests) + + ```bash + # In a brand new venv + pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple --no-cache anta + ``` +8. Push to anta repository and create a Pull Request + + ```bash + git push origin HEAD + gh pr create --title 'bump: ANTA vx.x.x' + ``` +9. Merge PR after review and wait for [workflow](https://github.com/arista-netdevops-community/anta/actions/workflows/release.yml) to be executed. + + ```bash + gh pr merge --squash + ``` + +10. Like 7 but for normal pypi + + ```bash + # In a brand new venv + pip install anta + ``` + +11. Test installed version + + ```bash + anta --version + ```
\ No newline at end of file diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..63657e0 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,59 @@ +changelog: + exclude: + labels: + - 'rn: test(anta)' + - 'rn: ci(anta)' + - 'rn: test(anta.tests)' + - 'rn: ci(anta.tests)' + - 'rn: test(anta.cli)' + - 'rn: ci(anta.cli)' + - 'rn: test' + - 'rn: ci' + categories: + - title: Breaking Changes + labels: + - 'rn: feat(anta)!' + - 'rn: feat(anta.tests)!' + - 'rn: feat(anta.cli)!' + - 'rn: fix(anta)!' + - 'rn: fix(anta.tests)!' + - 'rn: fix(anta.cli)!' + - 'rn: cut(anta)!' + - 'rn: cut(anta.tests)!' + - 'rn: cut(anta.cli)!' + - 'rn: revert(anta)!' + - 'rn: revert(anta.tests)!' + - 'rn: revert(anta.cli)!' + - 'rn: refactor(anta)!' + - 'rn: refactor(anta.tests)!' + - 'rn: refactor(anta.cli)!' + - 'rn: bump(anta)!' + - 'rn: bump(anta.tests)!' + - 'rn: bump(anta.cli)!' + - 'rn: feat!' + - 'rn: fix!' + - 'rn: cut!' + - 'rn: revert!' + - 'rn: refactor!' + - 'rn: bump!' + - title: New features and enhancements + labels: + - 'rn: feat(anta)' + - 'rn: feat(anta.tests)' + - 'rn: feat(anta.cli)' + - 'rn: feat' + - title: Fixed issues + labels: + - 'rn: fix(anta)' + - 'rn: fix(anta.tests)' + - 'rn: fix(anta.cli)' + - 'rn: fix' + - title: Documentation + labels: + - 'rn: doc(anta)' + - 'rn: doc(anta.tests)' + - 'rn: doc(anta.cli)' + - 'rn: doc' + - title: Other Changes + labels: + - '*' diff --git a/.github/workflows/code-testing.yml b/.github/workflows/code-testing.yml new file mode 100644 index 0000000..4d4c0a6 --- /dev/null +++ b/.github/workflows/code-testing.yml @@ -0,0 +1,144 @@ +--- +name: Linting and Testing Anta +on: + push: + branches: + - main + pull_request: + +jobs: + file-changes: + runs-on: ubuntu-latest + outputs: + code: ${{ steps.filter.outputs.code }} + docs: ${{ steps.filter.outputs.docs }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + code: + - 'anta/*' + - 'anta/**' + - 'tests/*' + - 'tests/**' + core: + - 'anta/*' + - 'anta/reporter/*' + - 'anta/result_manager/*' + - 'anta/tools/*' + cli: + - 'anta/cli/*' + - 'anta/cli/**' + tests: + - 'anta/tests/*' + - 'anta/tests/**' + docs: + - '.github/workflows/pull-request-management.yml' + - 'mkdocs.yml' + - 'docs/*' + - 'docs/**' + - 'README.md' + check-requirements: + runs-on: ubuntu-20.04 + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + needs: file-changes + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: install requirements + run: | + pip install . + - name: install dev requirements + run: pip install .[dev] + missing-documentation: + name: "Warning documentation is missing" + runs-on: ubuntu-20.04 + needs: [file-changes] + if: needs.file-changes.outputs.cli == 'true' && needs.file-changes.outputs.docs == 'false' + steps: + - name: Documentation is missing + uses: GrantBirki/comment@v2.0.9 + with: + body: | + Please consider that documentation is missing under `docs/` folder. + You should update documentation to reflect your change, or maybe not :) + lint-yaml: + name: Run linting for yaml files + runs-on: ubuntu-20.04 + needs: [file-changes, check-requirements] + if: needs.file-changes.outputs.code == 'true' + steps: + - uses: actions/checkout@v4 + - name: yaml-lint + uses: ibiqlik/action-yamllint@v3 + with: + config_file: .yamllint.yml + file_or_dir: . + lint-python: + name: Run isort, black, flake8 and pylint + runs-on: ubuntu-20.04 + needs: file-changes + if: needs.file-changes.outputs.code == 'true' + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: pip install tox + - name: "Run tox linting environment" + run: tox -e lint + type-python: + name: Run mypy + runs-on: ubuntu-20.04 + needs: file-changes + if: needs.file-changes.outputs.code == 'true' + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: pip install tox + - name: "Run tox typing environment" + run: tox -e type + test-python: + name: Pytest across all supported python versions + runs-on: ubuntu-20.04 + needs: [lint-python, type-python] + strategy: + matrix: + python: ["3.8", "3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: pip install tox tox-gh-actions + - name: "Run pytest via tox for ${{ matrix.python }}" + run: tox + test-documentation: + name: Build offline documentation for testing + runs-on: ubuntu-20.04 + needs: [lint-python, type-python, test-python] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: pip install .[doc] + - name: "Build mkdocs documentation offline" + run: mkdocs build diff --git a/.github/workflows/main-doc.yml b/.github/workflows/main-doc.yml new file mode 100644 index 0000000..0d46fa9 --- /dev/null +++ b/.github/workflows/main-doc.yml @@ -0,0 +1,37 @@ +--- +# This is deploying the latest commits on main to main documentation +name: Mkdocs +on: + push: + branches: + - main + paths: + # Run only if any of the following paths are changed when pushing to main + # May need to update this + - "docs/**" + - "mkdocs.yml" + workflow_dispatch: + +jobs: + 'build_latest_doc': + name: 'Update Public main documentation' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 'Setup Python 3 on runner' + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Setup Git config + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + - name: 'Build mkdocs content and deploy to gh-pages to main' + run: | + pip install .[doc] + mike deploy --push main diff --git a/.github/workflows/on-demand.yml b/.github/workflows/on-demand.yml new file mode 100644 index 0000000..85e7c41 --- /dev/null +++ b/.github/workflows/on-demand.yml @@ -0,0 +1,49 @@ +name: 'Build docker on-demand' +on: + workflow_dispatch: + inputs: + tag: + description: 'docker container tag' + required: true + type: string + default: 'dev' + +jobs: + docker: + name: Docker Image Build + runs-on: ubuntu-latest + strategy: + matrix: + platform: + - linux/amd64 + - linux/arm64 + - linux/arm/v7 + - linux/arm/v8 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker meta for TAG + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,value=${{ inputs.tag }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/pr-conflicts.yml b/.github/workflows/pr-conflicts.yml new file mode 100644 index 0000000..a126268 --- /dev/null +++ b/.github/workflows/pr-conflicts.yml @@ -0,0 +1,18 @@ +name: "PR Conflicts checker" +on: + pull_request_target: + types: [synchronize] + +jobs: + Conflict_Check: + name: 'Check PR status: conflicts and resolution' + runs-on: ubuntu-latest + steps: + - name: check if PRs are dirty + uses: eps1lon/actions-label-merge-conflict@releases/2.x + with: + dirtyLabel: "state: conflict" + removeOnDirtyLabel: "state: conflict resolved" + repoToken: "${{ secrets.GITHUB_TOKEN }}" + commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." + commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly." diff --git a/.github/workflows/pr-triage.yml b/.github/workflows/pr-triage.yml new file mode 100644 index 0000000..d60937d --- /dev/null +++ b/.github/workflows/pr-triage.yml @@ -0,0 +1,73 @@ +name: "Pull Request Triage" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + assign_author: + name: "Assign Author to PR" + # https://github.com/marketplace/actions/auto-author-assign + runs-on: ubuntu-latest + steps: + - uses: toshimaru/auto-author-assign@v2.1.0 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + + check_pr_semantic: + runs-on: ubuntu-latest + steps: + # Please look up the latest version from + # https://github.com/amannn/action-semantic-pull-request/releases + - uses: amannn/action-semantic-pull-request@v5.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + # Default: https://github.com/commitizen/conventional-commit-types + # Updated as part of PR 1930 + types: | + feat + fix + cut + doc + ci + bump + test + refactor + revert + make + chore + # Configure which scopes are allowed. + scopes: | + anta + anta.tests + anta.cli + # Configure that a scope must always be provided. + requireScope: false + # Configure additional validation for the subject based on a regex. + # This example ensures the subject doesn't start with an uppercase character. + # subjectPattern: ^(?![A-Z]).+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + subjectPatternError: | + The subject "{subject}" found in the pull request title "{title}" + didn't match the configured pattern. Please ensure that the subject + doesn't start with an uppercase character. + # When using "Squash and merge" on a PR with only one commit, GitHub + # will suggest using that commit message instead of the PR title for the + # merge commit, and it's easy to commit this by mistake. Enable this option + # to also validate the commit message for one commit PRs. + # Update 13-Jul-2022 CH: GitHub now offers a toggle for this behavior. + # We have set that to always use the PR title, so this check is no longer needed. + validateSingleCommit: false + # Related to `validateSingleCommit` you can opt-in to validate that the PR + # title matches a single commit to avoid confusion. + validateSingleCommitMatchesPrTitle: true + ignoreLabels: | + bot + ignore-semantic-pull-request diff --git a/.github/workflows/pull-request-rn-labeler.yml b/.github/workflows/pull-request-rn-labeler.yml new file mode 100644 index 0000000..39da881 --- /dev/null +++ b/.github/workflows/pull-request-rn-labeler.yml @@ -0,0 +1,27 @@ +# This workflow is triggered after a PR is merged or when the title of a PR is +# changed post merge +name: "Label for Release Notes" + + +on: + pull_request_target: + types: + - closed + - edited # interested in post merge title changes + +jobs: + ################################################### + # Assign labels on merge to generate Release Notes + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-your-workflow-when-a-pull-request-merges + ################################################### + if_merged: + name: "PR was merged" + if: (github.event.pull_request.merged == true) && ( github.event.action == 'closed' || (github.event.action == 'edited' && github.event.changes.title != null) ) + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/rn-pr-labeler-action + with: + auto_create_label: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6b9088f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,110 @@ +--- +name: "Tag & Release management" +on: + release: + types: + - published + +jobs: + pypi: + name: Publish version to Pypi servers + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel build + - name: Build package + run: | + python -m build + - name: Publish package to Pypi + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + + release-coverage: + name: Updated ANTA release coverage badge + runs-on: ubuntu-20.04 + needs: [pypi] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install dependencies + run: pip install genbadge[coverage] tox tox-gh-actions + - name: "Run pytest via tox for ${{ matrix.python }}" + run: tox + - name: Generate coverage badge + run: genbadge coverage -i .coverage.xml -o badge/latest-release-coverage.svg + - name: Publish coverage badge to gh-pages branch + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: coverage-badge + folder: badge + release-doc: + name: "Publish documentation for release ${{github.ref_name}}" + runs-on: ubuntu-latest + needs: [release-coverage] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: 'Setup Python 3 on runner' + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Setup Git config + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + - name: 'Build mkdocs content to site folder' + run: | + pip install .[doc] + mike deploy --update-alias --push ${{github.ref_name}} stable + + docker: + name: Docker Image Build + runs-on: ubuntu-latest + needs: [pypi] + strategy: + matrix: + platform: + - linux/amd64 + - linux/arm64 + - linux/arm/v7 + - linux/arm/v8 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Docker meta for TAG + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=raw,value=latest + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: true + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} |