diff --git a/.ci/scripts/apply_templates.py b/.ci/scripts/apply_templates.py new file mode 100644 index 000000000..8292bc27e --- /dev/null +++ b/.ci/scripts/apply_templates.py @@ -0,0 +1,29 @@ +import sys +from pathlib import Path + +import toml +from cookiecutter.main import cookiecutter + +if __name__ == "__main__": + sections = [] + overwrite = True + + with open("pyproject.toml") as fp: + config = toml.load(fp)["tool"]["pulp_cli_template"] + + sections.append("ci") + + if config["docs"]: + sections.append("docs") + + cutter_path = Path(__file__).parent.parent.parent / "cookiecutter" + + for section in sections: + print(f"Apply {section} template") + cookiecutter( + str(cutter_path / section), + no_input=True, + extra_context=config, + overwrite_if_exists=overwrite, + output_dir="..", + ) diff --git a/.ci/scripts/create_release_branch.sh b/.ci/scripts/create_release_branch.sh index 9444ee04e..9410270df 100755 --- a/.ci/scripts/create_release_branch.sh +++ b/.ci/scripts/create_release_branch.sh @@ -27,6 +27,6 @@ find CHANGES/ \( -name "*.feature" -o -name "*.bugfix" -o -name "*.doc" -o -name sed -i -e "1 i \\${NEW_BRANCH}" docs/versions.txt git add docs/versions.txt -bumpversion minor --commit --message $'Bump version to {new_version}\n\n[noissue]' --allow-dirty +bump2version minor --commit --message $'Bump version to {new_version}\n\n[noissue]' --allow-dirty git push origin "${NEW_BRANCH}" diff --git a/.flake8 b/.flake8 deleted file mode 100644 index f96e99398..000000000 --- a/.flake8 +++ /dev/null @@ -1,7 +0,0 @@ -[flake8] -exclude = ./docs/*, */migrations/*, ./pulpcore/plugin/docs/* -ignore = W503,Q000,Q003,D100,D104,D106,D200,D202,D205,D400,D401,D402 -# E203: whitespace before ':'; https://github.com/psf/black/issues/279 -# E401: multiple imports on one line -extend-ignore = E203,E401 -max-line-length = 100 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4730b78c..55df5c670 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,7 @@ jobs: key: "${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/*constraints.lock', '**/setup.py', '**/pyproject.toml') }}" restore-keys: | ${{ runner.os }}-pip- + - name: "Set up Python" uses: "actions/setup-python@v5" with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ee70dfde6..ba4e29162 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -3,42 +3,43 @@ name: "CodeQL" on: push: branches: - - main + - "main" workflow_call: jobs: analyze: - name: Analyze - runs-on: ubuntu-latest + name: "Analyze" + runs-on: "ubuntu-latest" permissions: - actions: read - contents: read - security-events: write + actions: "read" + contents: "read" + security-events: "write" steps: - - name: Checkout repository - uses: actions/checkout@v4 - - uses: actions/cache@v4 + - name: "Checkout repository" + uses: "actions/checkout@v4" + - uses: "actions/cache@v4" with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/*constraints.lock', '**/setup.py', '**/pyproject.toml') }} + path: "~/.cache/pip" + key: "${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/*constraints.lock', '**/setup.py', '**/pyproject.toml') }}" restore-keys: | ${{ runner.os }}-pip- - - name: Set up Python - uses: actions/setup-python@v5 + + - name: "Set up Python" + uses: "actions/setup-python@v5" with: - python-version: '3.11' - - name: Manually install from sources + python-version: "3.11" + - name: "Manually install from sources" run: | python -m pip install -e . -e ./pulp-glue - echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + echo "CODEQL_PYTHON=$(which python)" >> "$GITHUB_ENV" + - name: "Initialize CodeQL" + uses: "github/codeql-action/init@v3" with: - languages: python + languages: "python" setup-python-dependencies: false - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + - name: "Perform CodeQL Analysis" + uses: "github/codeql-action/analyze@v3" with: category: "/language:python" diff --git a/.github/workflows/collect_changes.yml b/.github/workflows/collect_changes.yml index 128b4c547..91b47d3c5 100644 --- a/.github/workflows/collect_changes.yml +++ b/.github/workflows/collect_changes.yml @@ -1,31 +1,31 @@ -name: Collect changes +name: "Collect changes" on: workflow_call: workflow_dispatch: jobs: collect-changes: - runs-on: ubuntu-latest + runs-on: "ubuntu-latest" steps: - - uses: actions/checkout@v4 + - uses: "actions/checkout@v4" with: ref: "main" fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: "actions/setup-python@v5" with: python-version: "3.11" - - name: Setup git + - name: "Setup git" run: | git config user.name pulpbot git config user.email pulp-infra@redhat.com - - name: Collect changes + - name: "Collect changes" run: | pip install GitPython packaging toml python3 .ci/scripts/collect_changes.py - - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 + - name: "Create Pull Request" + uses: "peter-evans/create-pull-request@v5" with: - token: ${{ secrets.RELEASE_TOKEN }} + token: "${{ secrets.RELEASE_TOKEN }}" title: "Update Changelog" body: "" branch: "update_changes" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0de9704af..89f727c74 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: python: - - "3.9" + - "3.8" - "3.11" steps: - uses: "actions/checkout@v4" @@ -20,6 +20,7 @@ jobs: key: "${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/*constraints.lock', '**/setup.py', '**/pyproject.toml') }}" restore-keys: | ${{ runner.os }}-pip- + - name: "Download wheels" uses: "actions/download-artifact@v4" with: diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml index 1e6d50446..4b7ccdf87 100644 --- a/.github/workflows/publish_docs.yml +++ b/.github/workflows/publish_docs.yml @@ -16,10 +16,10 @@ jobs: runs-on: "ubuntu-latest" steps: - uses: "actions/checkout@v4" - - name: Set up Python - uses: actions/setup-python@v5 + - name: "Set up Python" + uses: "actions/setup-python@v5" with: - python-version: 3.11 + python-version: "3.11" - name: "Run publish docs script" env: PULP_DOCS_KEY: "${{ secrets.PULP_DOCS_KEY }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d715a8ca0..8237f8f23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,14 +22,14 @@ jobs: - python: "3.11" image_tag: "latest" - python: "3.6" - image_tag: "3.28" + image_tag: "3.39" - python: "3.7" - image_tag: "3.21" + image_tag: "3.28" lower_bounds: true - python: "3.8" image_tag: "3.22" - python: "3.9" - image_tag: "3.39" + image_tag: "3.21" pulp_api_root: "/relocated/djnd/" - python: "3.10" image_tag: "3.18" @@ -42,6 +42,7 @@ jobs: key: "${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/*constraints.lock', '**/setup.py', '**/pyproject.toml') }}" restore-keys: | ${{ runner.os }}-pip- + - name: "Download wheels" uses: "actions/download-artifact@v4" with: diff --git a/Makefile b/Makefile index c8cf5ad85..745293caf 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ + LANGUAGES=de GLUE_PLUGINS=$(notdir $(wildcard pulp-glue/pulp_glue/*)) CLI_PLUGINS=$(notdir $(wildcard pulpcore/cli/*)) @@ -18,7 +19,7 @@ black: black . lint: - find . -name '*.sh' -print0 | xargs -0 shellcheck -x + find tests .ci -name '*.sh' -print0 | xargs -0 shellcheck -x isort -c --diff . cd pulp-glue; isort -c --diff . black --diff --check . @@ -65,6 +66,5 @@ $(foreach LANGUAGE,$(LANGUAGES),pulpcore/cli/%/locale/$(LANGUAGE)/LC_MESSAGES/me msgfmt -o $@ $< compile_messages: $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) - .PHONY: build info black lint test servedocs site .PRECIOUS: $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) diff --git a/cookiecutter/ci/cookiecutter.json b/cookiecutter/ci/cookiecutter.json new file mode 100644 index 000000000..499d4aeba --- /dev/null +++ b/cookiecutter/ci/cookiecutter.json @@ -0,0 +1,17 @@ +{ + "app_label": "noname", + "glue": true, + "docs": false, + "translations": false, + "test_matrix": "- python: \"3.11\"\nimage_tag: \"nightly\"\npulp_api_root: \"/relocated/djnd/\"\n- python: \"3.6\"\nimage_tag: \"latest\"\nlower_bounds: true", + "__app_label_suffix": "{{ cookiecutter.app_label and '-' + cookiecutter.app_label }}", + "__project_name": "pulp-cli{{ cookiecutter.__app_label_suffix }}", + "__project_slug": "{{ cookiecutter.__project_name | lower | replace(' ', '_') }}", + "_copy_without_render": [ + ".github/workflows/collect_changes.yml", + ".github/workflows/pr.yml", + ".github/workflows/release.yml", + ".github/workflows/release_branch.yml", + ".github/workflows/pr_checks.yml" + ] +} diff --git a/cookiecutter/ci/templates/cache_action b/cookiecutter/ci/templates/cache_action new file mode 100644 index 000000000..2906f2f61 --- /dev/null +++ b/cookiecutter/ci/templates/cache_action @@ -0,0 +1,8 @@ +{%- raw %} + - uses: "actions/cache@v4" + with: + path: "~/.cache/pip" + key: "${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/*constraints.lock', '**/setup.py', '**/pyproject.toml') }}" + restore-keys: | + ${{ runner.os }}-pip- +{%- endraw %} diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/run_container.sh b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/run_container.sh new file mode 100755 index 000000000..3730ff0c0 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/run_container.sh @@ -0,0 +1,89 @@ +#!/bin/sh + +# This file is shared between some projects please keep all copies in sync +# Known places: +# - https://github.com/pulp/pulp-cli/blob/main/.ci/run_container.sh +# - https://github.com/pulp/pulp-cli-deb/blob/main/.ci/run_container.sh +# - https://github.com/pulp/pulp-cli-gem/blob/main/.ci/run_container.sh +# - https://github.com/pulp/pulp-cli-maven/blob/main/.ci/run_container.sh +# - https://github.com/pulp/pulp-cli-ostree/blob/main/.ci/run_container.sh +# - https://github.com/pulp/squeezer/blob/develop/tests/run_container.sh + +set -eu + +BASEPATH="$(dirname "$(readlink -f "$0")")" +export BASEPATH + +if [ -z "${CONTAINER_RUNTIME:+x}" ] +then + if ls /usr/bin/podman + then + CONTAINER_RUNTIME=podman + else + CONTAINER_RUNTIME=docker + fi +fi +export CONTAINER_RUNTIME + +if [ -z "${KEEP_CONTAINER:+x}" ] +then + RM="yes" +else + RM="" +fi + +IMAGE_TAG="${IMAGE_TAG:-latest}" +FROM_TAG="${FROM_TAG:-latest}" + +if [ "${CONTAINER_FILE:+x}" ] +then + IMAGE_TAG="ephemeral-build" + "${CONTAINER_RUNTIME}" build --file "${BASEPATH}/assets/${CONTAINER_FILE}" --build-arg FROM_TAG="${FROM_TAG}" --tag ghcr.io/pulp/pulp:"${IMAGE_TAG}" . +fi + +if [ "$(getenforce)" = "Enforcing" ]; then + SELINUX="yes" +else + SELINUX="" +fi; + +"${CONTAINER_RUNTIME}" run ${RM:+--rm} --env S6_KEEP_ENV=1 ${PULP_API_ROOT:+--env PULP_API_ROOT} --detach --name "pulp-ephemeral" --volume "${BASEPATH}/settings:/etc/pulp${SELINUX:+:Z}" --publish "8080:80" "ghcr.io/pulp/pulp:${IMAGE_TAG}" + +# shellcheck disable=SC2064 +trap "${CONTAINER_RUNTIME} stop pulp-ephemeral" EXIT +# shellcheck disable=SC2064 +trap "${CONTAINER_RUNTIME} stop pulp-ephemeral" INT + +echo "Wait for pulp to start." +for counter in $(seq 40 -1 0) +do + if [ "$counter" = "0" ] + then + echo "FAIL." + "${CONTAINER_RUNTIME}" images + "${CONTAINER_RUNTIME}" ps -a + "${CONTAINER_RUNTIME}" logs "pulp-ephemeral" + exit 1 + fi + + sleep 3 + if curl --fail "http://localhost:8080${PULP_API_ROOT:-/pulp/}api/v3/status/" > /dev/null 2>&1 + then + echo "SUCCESS." + break + fi + echo "." +done + +# show pulpcore/plugin versions we're using +curl -s "http://localhost:8080${PULP_API_ROOT:-/pulp/}api/v3/status/" | jq '.versions|map({key: .component, value: .version})|from_entries' + +# Set admin password +"${CONTAINER_RUNTIME}" exec "pulp-ephemeral" pulpcore-manager reset-admin-password --password password + +if [ -d "${BASEPATH}/container_setup.d/" ] +then + run-parts --regex '^[0-9]+-[-_[:alnum:]]*\.sh$' "${BASEPATH}/container_setup.d/" +fi + +PULP_LOGGING="${CONTAINER_RUNTIME}" "$@" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/check_click_for_mypy.py b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/check_click_for_mypy.py new file mode 100755 index 000000000..e3b50f484 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/check_click_for_mypy.py @@ -0,0 +1,11 @@ +#!/bin/env python3 + +import sys + +import click +from packaging.version import parse + +if parse(click.__version__) < parse("8.1.1") or parse(click.__version__) >= parse("8.2"): + print("🚧 Linting with mypy is currently only supported with click~=8.1.1. 🚧") + print("🔧 Please run `pip install click~=8.1.1` first. 🔨") + sys.exit(1) diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/collect_changes.py b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/collect_changes.py new file mode 100755 index 000000000..3565db456 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/collect_changes.py @@ -0,0 +1,96 @@ +import itertools +import os +import re + +import toml +from git import GitCommandError, Repo +from packaging.version import parse as parse_version + +# Read Towncrier settings +tc_settings = toml.load("pyproject.toml")["tool"]["towncrier"] + +CHANGELOG_FILE = tc_settings.get("filename", "NEWS.rst") +START_STRING = tc_settings.get( + "start_string", + ( + "\n" + if CHANGELOG_FILE.endswith(".md") + else ".. towncrier release notes start\n" + ), +) +TITLE_FORMAT = tc_settings.get("title_format", "{name} {version} ({project_date})") + + +NAME_REGEX = r".*" +VERSION_REGEX = r"([0-9]+\.[0-9]+\.[0-9][0-9ab]*)" +DATE_REGEX = r"[0-9]{4}-[0-9]{2}-[0-9]{2}" +TITLE_REGEX = ( + "(" + + re.escape( + TITLE_FORMAT.format(name="NAME_REGEX", version="VERSION_REGEX", project_date="DATE_REGEX") + ) + .replace("NAME_REGEX", NAME_REGEX) + .replace("VERSION_REGEX", VERSION_REGEX) + .replace("DATE_REGEX", DATE_REGEX) + + ")" +) + + +def get_changelog(repo, branch): + return repo.git.show(f"{branch}:{CHANGELOG_FILE}") + "\n" + + +def _tokenize_changes(splits): + assert len(splits) % 3 == 0 + for i in range(len(splits) // 3): + title = splits[3 * i] + version = parse_version(splits[3 * i + 1]) + yield [version, title + splits[3 * i + 2]] + + +def split_changelog(changelog): + preamble, rest = changelog.split(START_STRING, maxsplit=1) + split_rest = re.split(TITLE_REGEX, rest) + return preamble + START_STRING + split_rest[0], list(_tokenize_changes(split_rest[1:])) + + +def main(): + repo = Repo(os.getcwd()) + remote = repo.remotes[0] + branches = [ref for ref in remote.refs if re.match(r"^([0-9]+)\.([0-9]+)$", ref.remote_head)] + branches.sort(key=lambda ref: parse_version(ref.remote_head), reverse=True) + branches = [ref.name for ref in branches] + + with open(CHANGELOG_FILE, "r") as f: + main_changelog = f.read() + preamble, main_changes = split_changelog(main_changelog) + old_length = len(main_changes) + + for branch in branches: + print(f"Looking at branch {branch}") + try: + changelog = get_changelog(repo, branch) + except GitCommandError: + print("No changelog found on this branch.") + continue + dummy, changes = split_changelog(changelog) + new_changes = sorted(main_changes + changes, key=lambda x: x[0], reverse=True) + # Now remove duplicates (retain the first one) + main_changes = [new_changes[0]] + for left, right in itertools.pairwise(new_changes): + if left[0] != right[0]: + main_changes.append(right) + + new_length = len(main_changes) + if old_length < new_length: + print(f"{new_length - old_length} new versions have been added.") + with open(CHANGELOG_FILE, "w") as fp: + fp.write(preamble) + for change in main_changes: + fp.write(change[1]) + + repo.git.commit("-m", "Update Changelog", "-m" "[noissue]", CHANGELOG_FILE) + + +if __name__ == "__main__": + main() diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/create_release_branch.sh b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/create_release_branch.sh new file mode 100755 index 000000000..e2c2ac939 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/create_release_branch.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -eu -o pipefail + +BRANCH="$(git branch --show-current)" + +if ! [[ "${BRANCH}" = "main" ]] +then + echo ERROR: This is not the main branch! + exit 1 +fi + +NEW_BRANCH="$(bump2version --dry-run --list release | sed -Ene 's/^new_version=([[:digit:]]+\.[[:digit:]]+)\..*$/\1/p')" + +if [[ -z "${NEW_BRANCH}" ]] +then + echo ERROR: Could not parse new version. + exit 1 +fi + +git branch "${NEW_BRANCH}" + +# Clean changelog snippets. +find CHANGES/ \( -name "*.feature" -o -name "*.bugfix" -o -name "*.doc" -o -name "*.translation" -o -name "*.devel" -o -name "*.misc" \) -exec git rm -f \{\} + +{%- if cookiecutter.docs %} + +# Add to version.txt +sed -i -e "1 i \\${NEW_BRANCH}" docs/versions.txt +git add docs/versions.txt +{%- endif %} + +bump2version minor --commit --message $'Bump version to {new_version}\n\n[noissue]' --allow-dirty + +git push origin "${NEW_BRANCH}" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/release.sh b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/release.sh new file mode 100755 index 000000000..9525f229d --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/release.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -eu -o pipefail + +BRANCH=$(git branch --show-current) + +if ! [[ "${BRANCH}" =~ ^[0-9]+\.[0-9]+$ ]] +then + echo ERROR: This is not a release branch! + exit 1 +fi + +NEW_VERSION="$(bump2version --dry-run --list release | sed -ne 's/^new_version=//p')" +echo "Release ${NEW_VERSION}" + +if ! [[ "${NEW_VERSION}" == "${BRANCH}"* ]] +then + echo ERROR: Version does not match release branch + exit 1 +fi + +towncrier build --yes --version "${NEW_VERSION}" +bump2version release --commit --message "Release {new_version}" --tag --tag-name "{new_version}" --tag-message "Release {new_version}" --allow-dirty +bump2version patch --commit + +git push origin "${BRANCH}" "${NEW_VERSION}" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/validate_commit_message.py b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/validate_commit_message.py new file mode 100644 index 000000000..45390eb04 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.ci/scripts/validate_commit_message.py @@ -0,0 +1,73 @@ +import os +import re +import subprocess +import sys +from pathlib import Path + +import toml +from github import Github + +KEYWORDS = ["fixes", "closes"] +BLOCKING_REGEX = [ + "DRAFT", + "WIP", + "NOMERGE", + r"DO\s*NOT\s*MERGE", + "EXPERIMENT", +] +NO_ISSUE = "[noissue]" +CHANGELOG_EXTS = [ + f".{item['directory']}" for item in toml.load("pyproject.toml")["tool"]["towncrier"]["type"] +] + +sha = sys.argv[1] +message = subprocess.check_output(["git", "log", "--format=%B", "-n 1", sha]).decode("utf-8") + +if any((re.match(pattern, message) for pattern in BLOCKING_REGEX)): + sys.exit("This PR is not ready for consumption.") + +g = Github(os.environ.get("GITHUB_TOKEN")) +repo = g.get_repo("pulp/pulp-cli{{ cookiecutter.__app_label_suffix }}") + + +def check_status(issue): + gi = repo.get_issue(int(issue)) + if gi.pull_request: + sys.exit(f"Error: issue #{issue} is a pull request.") + if gi.closed_at: + sys.exit(f"Error: issue #{issue} is closed.") + + +def check_changelog(issue): + matches = list(Path("CHANGES").rglob(f"{issue}.*")) + + if len(matches) < 1: + sys.exit(f"Could not find changelog entry in CHANGES/ for {issue}.") + for match in matches: + if match.suffix not in CHANGELOG_EXTS: + sys.exit(f"Invalid extension for changelog entry '{match}'.") + + +print("Checking commit message for {sha}.".format(sha=sha[0:7])) + +# validate the issue attached to the commit +issue_regex = r"(?:{keywords})[\s:]+#(\d+)".format(keywords=("|").join(KEYWORDS)) +issues = re.findall(issue_regex, message, re.IGNORECASE) +cherry_pick_regex = r"^\s*\(cherry picked from commit [0-9a-f]*\)\s*$" +cherry_pick = re.search(cherry_pick_regex, message, re.MULTILINE) + +if issues: + for issue in issues: + if not cherry_pick: + check_status(issue) + check_changelog(issue) +else: + if NO_ISSUE in message: + print("Commit {sha} has no issues but is tagged {tag}.".format(sha=sha[0:7], tag=NO_ISSUE)) + else: + sys.exit( + "Error: no attached issues found for {sha}. If this was intentional, add " + " '{tag}' to the commit message.".format(sha=sha[0:7], tag=NO_ISSUE) + ) + +print("Commit message for {sha} passed.".format(sha=sha[0:7])) diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/dependabot.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/dependabot.yml new file mode 100644 index 000000000..fba8440c5 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + commit-message: + prefix: "[noissue]" +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + commit-message: + prefix: "[noissue]" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/build.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/build.yml new file mode 100644 index 000000000..931439ffe --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: "Build" + +on: + workflow_call: + +jobs: + build: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + {%- include "cache_action" %} + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.11" + - name: "Install python dependencies" + run: | + pip install build setuptools wheel + - name: "Build wheels" + run: | + make build + - name: "Upload wheels" + uses: "actions/upload-artifact@v4" + with: + name: "pulp_cli_packages" + path: | + pulp-glue{{ cookiecutter.__app_label_suffix }}/dist/ + dist/ + if-no-files-found: "error" + retention-days: 5 diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/codeql.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/codeql.yml new file mode 100644 index 000000000..332acd4bf --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/codeql.yml @@ -0,0 +1,39 @@ +name: "CodeQL" + +on: + push: + branches: + - "main" + workflow_call: + +jobs: + analyze: + name: "Analyze" + runs-on: "ubuntu-latest" + permissions: + actions: "read" + contents: "read" + security-events: "write" + + steps: + - name: "Checkout repository" + uses: "actions/checkout@v4" + {%- include "cache_action" %} + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.11" + - name: "Manually install from sources" + run: | + python -m pip install -e . {%- if cookiecutter.glue %} -e ./pulp-glue{{ cookiecutter.__app_label_suffix }} {%- endif %} + echo "CODEQL_PYTHON=$(which python)" >> "$GITHUB_ENV" + - name: "Initialize CodeQL" + uses: "github/codeql-action/init@v3" + with: + languages: "python" + setup-python-dependencies: false + + - name: "Perform CodeQL Analysis" + uses: "github/codeql-action/analyze@v3" + with: + category: "/language:python" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/collect_changes.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/collect_changes.yml new file mode 100644 index 000000000..91b47d3c5 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/collect_changes.yml @@ -0,0 +1,32 @@ +name: "Collect changes" +on: + workflow_call: + workflow_dispatch: + +jobs: + collect-changes: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + with: + ref: "main" + fetch-depth: 0 + - uses: "actions/setup-python@v5" + with: + python-version: "3.11" + - name: "Setup git" + run: | + git config user.name pulpbot + git config user.email pulp-infra@redhat.com + - name: "Collect changes" + run: | + pip install GitPython packaging toml + python3 .ci/scripts/collect_changes.py + - name: "Create Pull Request" + uses: "peter-evans/create-pull-request@v5" + with: + token: "${{ secrets.RELEASE_TOKEN }}" + title: "Update Changelog" + body: "" + branch: "update_changes" + delete-branch: true diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/lint.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/lint.yml new file mode 100644 index 000000000..bcbcdd72b --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/lint.yml @@ -0,0 +1,31 @@ +name: "Lint" + +on: + workflow_call: + +jobs: + lint: + runs-on: "ubuntu-latest" + strategy: + fail-fast: false + matrix: + python: + - "3.8" + - "3.11" + steps: + - uses: "actions/checkout@v4" + {%- include "cache_action" %} + - name: "Download wheels" + uses: "actions/download-artifact@v4" + with: + name: "pulp_cli_packages" + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "{{ "${{ matrix.python }}" }}" + - name: "Install python dependencies" + run: | + pip install dist/pulp_cli{{ cookiecutter.__app_label_suffix | replace ("-", "_") }}-*.whl {%- if cookiecutter.glue %} pulp-glue{{ cookiecutter.__app_label_suffix }}/dist/pulp_glue{{ cookiecutter.__app_label_suffix | replace ("-", "_") }}-*.whl {%- endif %} -r lint_requirements.txt + - name: "Lint code" + run: | + make lint diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/nightly.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/nightly.yml new file mode 100644 index 000000000..c4e2cca74 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/nightly.yml @@ -0,0 +1,19 @@ +name: "pulp-cli Nightly" + +on: + schedule: + - cron: "15 3 * * *" + workflow_dispatch: + +jobs: + build: + uses: "./.github/workflows/build.yml" + test: + needs: + - "build" + uses: "./.github/workflows/test.yml" + codeql: + uses: "./.github/workflows/codeql.yml" + collect_changes: + uses: "./.github/workflows/collect_changes.yml" + secrets: "inherit" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/pr.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/pr.yml new file mode 100644 index 000000000..de3ef996f --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/pr.yml @@ -0,0 +1,66 @@ +name: "pulp-cli CI" + +on: + pull_request: + +concurrency: + group: "main-${{ github.ref_name }}-${{ github.workflow }}" + cancel-in-progress: true + +jobs: + build: + uses: "./.github/workflows/build.yml" + lint: + needs: + - "build" + uses: "./.github/workflows/lint.yml" + test: + needs: + - "lint" + uses: "./.github/workflows/test.yml" + codeql: + needs: + - "lint" + uses: "./.github/workflows/codeql.yml" + check-commits: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + with: + fetch-depth: 0 + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.11" + - name: "Install python dependencies" + run: | + pip install toml pygithub + - name: "Check commit message" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + GITHUB_CONTEXT: "${{ github.event.pull_request.commits_url }}" + run: | + for SHA in $(curl -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_CONTEXT" | jq -r '.[].sha') + do + python .ci/scripts/validate_commit_message.py "$SHA" + VALUE=$? + if [ "$VALUE" -gt 0 ]; then + exit "$VALUE" + fi + done + shell: "bash" + ready-to-ship: + # This is a dummy dependent task to have a single entry for the branch protection rules. + runs-on: "ubuntu-latest" + needs: + - "check-commits" + - "lint" + - "test" + - "codeql" + if: "always()" + steps: + - name: "Collect needed jobs results" + run: | + echo '${{toJson(needs)}}' | jq -r 'to_entries[]|select(.value.result!="success")|.key + ": " + .value.result' + echo '${{toJson(needs)}}' | jq -e 'to_entries|map(select(.value.result!="success"))|length == 0' + echo "CI says: Looks good!" diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/pr_checks.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/pr_checks.yml new file mode 100644 index 000000000..f1f8aa25b --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/pr_checks.yml @@ -0,0 +1,55 @@ +--- +name: "Pulp CLI PR static checks" +on: + pull_request_target: + types: ["opened", "synchronize", "reopened"] + +# This workflow runs with elevated permissions. +# Do not even think about running a single bit of code from the PR. +# Static analysis should be fine however. + +concurrency: + group: "${{ github.event.pull_request.number }}-${{ github.workflow }}" + cancel-in-progress: true + +jobs: + single_commit: + runs-on: "ubuntu-latest" + name: "Label multiple commit PR" + permissions: + pull-requests: "write" + steps: + - uses: "actions/checkout@v4" + with: + fetch-depth: 0 + - name: "Commit Count Check" + run: | + git fetch origin ${{ github.event.pull_request.head.sha }} + echo "COMMIT_COUNT=$(git log --oneline --no-merges origin/${{ github.base_ref }}..${{ github.event.pull_request.head.sha }} | wc -l)" >> "$GITHUB_ENV" + - uses: "actions/github-script@v7" + with: + script: | + const labelName = "multi-commit"; + const { COMMIT_COUNT } = process.env; + + if (COMMIT_COUNT == 1) + { + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + }); + } catch(err) { + } + } + else + { + await github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: [labelName], + }); + } diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/publish.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/publish.yml new file mode 100644 index 000000000..0bc33a45b --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/publish.yml @@ -0,0 +1,44 @@ +name: "pulp-cli Publish" + +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" + +jobs: + build: + uses: "./.github/workflows/build.yml" + publish-pypi: + name: "Publish to PyPI" + needs: "build" + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + - name: "Download wheels" + uses: "actions/download-artifact@v4" + with: + name: "pulp_cli_packages" + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.x" + - name: "Install dependencies" + run: | + python -m pip install --upgrade pip + pip install twine + - name: "Build and publish" + env: + TWINE_USERNAME: "__token__" + TWINE_PASSWORD: "{{ "${{ secrets.PYPI_API_TOKEN }}" }}" + run: | + cd pulp-glue{{ cookiecutter.__app_label_suffix }} + twine upload dist/* + cd .. + twine upload dist/* +{%- if cookiecutter.docs %} + publish-docs: + needs: + - "publish-pypi" + uses: "./.github/workflows/publish_docs.yml" + secrets: "inherit" +{%- endif %} diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/release.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/release.yml new file mode 100644 index 000000000..7534d7b53 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: "pulp-cli Release" + +on: + workflow_dispatch: + +jobs: + release: + name: "Release" + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + with: + token: "${{ secrets.RELEASE_TOKEN }}" + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.x" + - name: "Install dependencies" + run: | + python -m pip install --upgrade pip + pip install bump2version towncrier~=23.11.0 + - name: "Setup git" + run: | + git config user.name pulpbot + git config user.email pulp-infra@redhat.com + - name: "Release" + run: | + .ci/scripts/release.sh diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/release_branch.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/release_branch.yml new file mode 100644 index 000000000..b89a9b062 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/release_branch.yml @@ -0,0 +1,32 @@ +--- +name: "Create Release Branch" +on: + workflow_dispatch: + +jobs: + create-release-branch: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.11" + - name: "Setup git" + run: | + git config user.name pulpbot + git config user.email pulp-infra@redhat.com + - name: "Install python dependencies" + run: | + pip install bump2version + - name: "Create Release Branch" + run: | + .ci/scripts/create_release_branch.sh + - name: "Create Pull Request" + uses: "peter-evans/create-pull-request@v5" + with: + token: "${{ secrets.RELEASE_TOKEN }}" + title: "Bump dev-version" + body: "" + branch: "bump_version" + delete-branch: true diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/test.yml b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/test.yml new file mode 100644 index 000000000..b153b3fc6 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/.github/workflows/test.yml @@ -0,0 +1,48 @@ +name: "Test" + +on: + workflow_call: + +env: + COLORTERM: "yes" + TERM: "xterm-256color" + PYTEST_ADDOPTS: "--color=yes" + CONTAINER_RUNTIME: "docker" + +jobs: + test: + runs-on: "ubuntu-20.04" + strategy: + fail-fast: false + matrix: + include: + {{ cookiecutter.test_matrix | indent(10) }} + steps: + - uses: "actions/checkout@v4" + {%- include "cache_action" %} + - name: "Download wheels" + uses: "actions/download-artifact@v4" + with: + name: "pulp_cli_packages" + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "{{ "${{ matrix.python }}" }}" + - name: "Install Test Dependencies" + run: | + if [ "{{ "${{matrix.lower_bounds}}" }}" ] + then + pip install dist/pulp_cli{{ cookiecutter.__app_label_suffix | replace("-", "_") }}-*.whl {%- if cookiecutter.glue %} pulp-glue{{ cookiecutter.__app_label_suffix }}/dist/pulp_glue{{ cookiecutter.__app_label_suffix | replace("-", "_") }}-*.whl {%- endif %} -r test_requirements.txt -c lower_bounds_constraints.lock + else + pip install dist/pulp_cli{{ cookiecutter.__app_label_suffix | replace("-", "_") }}-*.whl {%- if cookiecutter.glue %} pulp-glue{{ cookiecutter.__app_label_suffix }}/dist/pulp_glue{{ cookiecutter.__app_label_suffix | replace("-", "_") }}-*.whl {%- endif %} -r test_requirements.txt + fi + - name: Run tests + env: + {%- raw %} + CONTAINER_RUNTIME: ${{ matrix.container_runtime }} + IMAGE_TAG: ${{ matrix.image_tag }} + FROM_TAG: ${{ matrix.from_tag }} + CONTAINER_FILE: ${{ matrix.container_file }} + PULP_API_ROOT: ${{ matrix.pulp_api_root }} + {%- endraw %} + run: .ci/run_container.sh make test diff --git a/cookiecutter/ci/{{ cookiecutter.__project_slug }}/Makefile b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/Makefile new file mode 100644 index 000000000..19869fb72 --- /dev/null +++ b/cookiecutter/ci/{{ cookiecutter.__project_slug }}/Makefile @@ -0,0 +1,96 @@ +{%- if cookiecutter.translations %} +LANGUAGES=de +{%- endif %} +{%- if cookiecutter.glue %} +GLUE_PLUGINS=$(notdir $(wildcard pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/*)) +{%- endif %} +CLI_PLUGINS=$(notdir $(wildcard pulpcore/cli/*)) + +info: +{%- if cookiecutter.glue %} + @echo Pulp glue + @echo plugins: $(GLUE_PLUGINS) +{%- endif %} + @echo Pulp CLI + @echo plugins: $(CLI_PLUGINS) + +build: +{%- if cookiecutter.glue %} + cd pulp-glue{{ cookiecutter.__app_label_suffix }}; pyproject-build -n +{%- endif %} + pyproject-build -n + +black: + isort . +{%- if cookiecutter.glue %} + cd pulp-glue{{ cookiecutter.__app_label_suffix }}; isort . +{%- endif %} + black . + +lint: + find tests .ci -name '*.sh' -print0 | xargs -0 shellcheck -x + isort -c --diff . +{%- if cookiecutter.glue %} + cd pulp-glue{{ cookiecutter.__app_label_suffix }}; isort -c --diff . +{%- endif %} + black --diff --check . + flake8 + .ci/scripts/check_click_for_mypy.py +{%- if cookiecutter.glue %} + MYPYPATH=pulp-glue{{ cookiecutter.__app_label_suffix }} mypy + cd pulp-glue{{ cookiecutter.__app_label_suffix }}; mypy +{%- else %} + mypy +{%- endif %} + @echo "🙊 Code 🙈 LGTM 🙉 !" + +tests/cli.toml: + cp $@.example $@ + @echo "In order to configure the tests to talk to your test server, you might need to edit $@ ." + +test: | tests/cli.toml + pytest -v tests +{%- if cookiecutter.docs %} + +servedocs: + mkdocs serve -w docs_theme -w pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue -w pulpcore/cli/common/generic.py + +site: + mkdocs build +{%- endif %} +{%- if cookiecutter.translations %} +{%- if cookiecutter.glue %} + +pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/%/locale/messages.pot: pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/%/*.py + xgettext -d $* -o $@ pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/$*/*.py + sed -i 's/charset=CHARSET/charset=UTF-8/g' $@ +{%- endif %} + +pulpcore/cli/%/locale/messages.pot: pulpcore/cli/%/*.py + xgettext -d $* -o $@ pulpcore/cli/$*/*.py + sed -i 's/charset=CHARSET/charset=UTF-8/g' $@ + +extract_messages: {%- if cookiecutter.glue %} $(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/$(GLUE_PLUGIN)/locale/messages.pot) {%- endif %} $(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/messages.pot) +{%- if cookiecutter.glue %} + +$(foreach LANGUAGE,$(LANGUAGES),pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/%/locale/$(LANGUAGE)/LC_MESSAGES/messages.po): pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/%/locale/messages.pot + [ -e $(@D) ] || mkdir -p $(@D) + [ ! -e $@ ] || msgmerge --update $@ $< + [ -e $@ ] || cp $< $@ +{%- endif %} + +$(foreach LANGUAGE,$(LANGUAGES),pulpcore/cli/%/locale/$(LANGUAGE)/LC_MESSAGES/messages.po): pulpcore/cli/%/locale/messages.pot + [ -e $(@D) ] || mkdir -p $(@D) + [ ! -e $@ ] || msgmerge --update $@ $< + [ -e $@ ] || cp $< $@ + +%.mo: %.po + msgfmt -o $@ $< + +compile_messages: {%- if cookiecutter.glue %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) {%- endif %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.mo)) + +{%- endif %} +.PHONY: build info black lint test {%- if cookiecutter.docs %} servedocs site {%- endif %} +{%- if cookiecutter.translations %} +.PRECIOUS: {%- if cookiecutter.glue %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach GLUE_PLUGIN,$(GLUE_PLUGINS),pulp-glue{{ cookiecutter.__app_label_suffix }}/pulp_glue/$(GLUE_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) {%- endif %} $(foreach LANGUAGE,$(LANGUAGES),$(foreach CLI_PLUGIN,$(CLI_PLUGINS),pulpcore/cli/$(CLI_PLUGIN)/locale/$(LANGUAGE)/LC_MESSAGES/messages.po)) +{%- endif %} diff --git a/cookiecutter/docs/cookiecutter.json b/cookiecutter/docs/cookiecutter.json new file mode 100644 index 000000000..f69f7a062 --- /dev/null +++ b/cookiecutter/docs/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "app_label": "noname", + "docs": false, + "__app_label_suffix": "{{ cookiecutter.app_label and '-' + cookiecutter.app_label }}", + "__project_name": "pulp-cli{{ cookiecutter.__app_label_suffix }}", + "__project_slug": "{{ cookiecutter.__project_name | lower | replace(' ', '_') }}", + "_copy_without_render": [ + ".github/workflows/publish_docs.yml" + ] +} diff --git a/cookiecutter/docs/{{ cookiecutter.__project_slug }}/.ci/scripts/publish_docs.sh b/cookiecutter/docs/{{ cookiecutter.__project_slug }}/.ci/scripts/publish_docs.sh new file mode 100755 index 000000000..808e102f5 --- /dev/null +++ b/cookiecutter/docs/{{ cookiecutter.__project_slug }}/.ci/scripts/publish_docs.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -euv + +# make sure this script runs at the repo root +cd "$(dirname "$(realpath -e "$0")")"/../.. + +REF="${1#refs/}" +REF_NAME="${REF#*/}" +REF_TYPE="${REF%%/*}" + +mkdir ~/.ssh +echo "${PULP_DOCS_KEY}" > ~/.ssh/pulp-infra +chmod 600 ~/.ssh/pulp-infra + +echo "docs.pulpproject.org,8.43.85.236 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGXG+8vjSQvnAkq33i0XWgpSrbco3rRqNZr0SfVeiqFI7RN/VznwXMioDDhc+hQtgVhd6TYBOrV07IMcKj+FAzg=" >> /home/runner/.ssh/known_hosts +chmod 644 /home/runner/.ssh/known_hosts + +eval "$(ssh-agent -s)" #start the ssh agent +ssh-add ~/.ssh/pulp-infra + +make site + +if [ "${REF_TYPE}" = "heads" ] +then + [ "${REF_NAME}" = "main" ] + # publish to docs.pulpproject.org/pulp_cli{{ cookiecutter.__app_label_suffix | replace("-", "_") }} + rsync -avzh --delete-delay -f '+ en/index.html' -f'P en/*' site/ doc_builder_pulp_cli@docs.pulpproject.org:/var/www/docs.pulpproject.org/pulp_cli{{ cookiecutter.__app_label_suffix | replace("-", "_") }}/ +else + [ "${REF_TYPE}" = "tags" ] + # publish to docs.pulpproject.org/pulp_cli{{ cookiecutter.__app_label_suffix | replace("-", "_") }}/en/{release x.y} + rsync -avzh --delete-delay site/ doc_builder_pulp_cli@docs.pulpproject.org:/var/www/docs.pulpproject.org/pulp_cli{{ cookiecutter.__app_label_suffix | replace("-", "_") }}/en/"${REF_NAME%.*}" +fi diff --git a/cookiecutter/docs/{{ cookiecutter.__project_slug }}/.github/workflows/publish_docs.yml b/cookiecutter/docs/{{ cookiecutter.__project_slug }}/.github/workflows/publish_docs.yml new file mode 100644 index 000000000..4b7ccdf87 --- /dev/null +++ b/cookiecutter/docs/{{ cookiecutter.__project_slug }}/.github/workflows/publish_docs.yml @@ -0,0 +1,28 @@ +name: "pulp-cli Publish Docs" + +on: + push: + branches: + - "main" + paths: + - "docs/**" + - "doc_requirements.txt" + workflow_dispatch: + workflow_call: + +jobs: + publish-docs: + name: "Publish docs" + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v4" + - name: "Set up Python" + uses: "actions/setup-python@v5" + with: + python-version: "3.11" + - name: "Run publish docs script" + env: + PULP_DOCS_KEY: "${{ secrets.PULP_DOCS_KEY }}" + run: | + pip install -r doc_requirements.txt + .ci/scripts/publish_docs.sh "${GITHUB_REF}" diff --git a/lint_requirements.txt b/lint_requirements.txt index 0ee62a950..e1a0c4703 100644 --- a/lint_requirements.txt +++ b/lint_requirements.txt @@ -1,6 +1,7 @@ # Lint requirements black==24.1.0 flake8==7.0.0 +flake8-pyproject==1.2.3 isort==5.13.2 mypy==1.8.0 shellcheck-py==0.9.0.6 diff --git a/pyproject.toml b/pyproject.toml index 01f32e39e..dbbde4643 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,31 @@ namespaces = true [tool.setuptools.package-data] "*" = ["py.typed", "locale/*/LC_MESSAGES/*.mo"] +[tool.pulp_cli_template] +app_label = "" +docs = true +translations = true +test_matrix = """ +- python: "3.11" + image_tag: "nightly" + pulp_api_root: "/relocated/djnd/" +- python: "3.11" + image_tag: "latest" +- python: "3.6" + image_tag: "3.39" +- python: "3.7" + image_tag: "3.28" + lower_bounds: true +- python: "3.8" + image_tag: "3.22" +- python: "3.9" + image_tag: "3.21" + pulp_api_root: "/relocated/djnd/" +- python: "3.10" + image_tag: "3.18" + lower_bounds: true +""" + [tool.towncrier] filename = "docs/CHANGES.md" directory = "CHANGES/" @@ -121,6 +146,14 @@ profile = "black" line_length = 100 skip = ["pulp-glue"] +[tool.flake8] +exclude = ["./docs/*"] +ignore = ["W503", "Q000", "Q003", "D100", "D104", "D106", "D200", "D202", "D205", "D400", "D401", "D402"] +# E203: whitespace before ':'; https://github.com/psf/black/issues/279 +# E401: multiple imports on one line +extend-ignore = ["E203", "E401"] +max-line-length = 100 + [tool.pytest.ini_options] markers = [ "glue: tests for pulp-glue in isolation",