summaryrefslogtreecommitdiffstats
path: root/.github
diff options
context:
space:
mode:
Diffstat (limited to '.github')
-rw-r--r--.github/actions/rn-pr-labeler-action/action.yml41
-rw-r--r--.github/changelog.sh435
-rw-r--r--.github/dependabot.yml34
-rw-r--r--.github/generate_release.py117
-rw-r--r--.github/license-short.txt3
-rw-r--r--.github/pull_request_template.md16
-rw-r--r--.github/release.md103
-rw-r--r--.github/release.yml59
-rw-r--r--.github/workflows/code-testing.yml144
-rw-r--r--.github/workflows/main-doc.yml37
-rw-r--r--.github/workflows/on-demand.yml49
-rw-r--r--.github/workflows/pr-conflicts.yml18
-rw-r--r--.github/workflows/pr-triage.yml73
-rw-r--r--.github/workflows/pull-request-rn-labeler.yml27
-rw-r--r--.github/workflows/release.yml110
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 }}