From fc3c5e9f690f654bac8ae1c0c67c2ba37632f8ff Mon Sep 17 00:00:00 2001 From: Juan Esteban Villa Duarte Date: Wed, 23 Oct 2024 18:31:47 -0500 Subject: [PATCH] feat(core): added cicd flows --- .bumpversion.cfg | 6 + .github/FUNDING.yml | 15 + .github/ISSUE_TEMPLATE/bug_report.md | 40 ++ .github/ISSUE_TEMPLATE/config.yml | 11 + .github/ISSUE_TEMPLATE/feature_request.md | 19 + .github/dependabot.yml | 12 + .github/workflows/ci.yml | 80 +++ .github/workflows/greetings.yml | 18 + .github/workflows/node.yml.disabled | 31 + .github/workflows/python.yml | 34 ++ .github/workflows/release-controller.yml | 77 +++ .github/workflows/stale.yml | 28 + .github/workflows/version-controller.yml | 142 +++++ .gitignore | 134 +++++ .pre-commit-config.yaml | 83 +++ .pylintrc | 652 ++++++++++++++++++++++ CHANGELOG.md | 15 + CODE_OF_CONDUCT.md | 40 ++ CONTRIBUTING.md | 53 ++ INSTALL.md | 0 SECURITY.md | 51 ++ VERSIONING.md | 92 +++ pyproject.toml | 71 +++ requirements.txt | 27 + scripts/bump_year/main.py | 67 +++ scripts/commit_msg_version_bump/main.py | 123 ++++ scripts/format_yaml/main.py | 101 ++++ scripts/format_yml/main.py | 88 +++ scripts/generate_changelog/main.py | 276 +++++++++ scripts/validate_docker_compose/main.py | 90 +++ 30 files changed, 2476 insertions(+) create mode 100644 .bumpversion.cfg create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/greetings.yml create mode 100644 .github/workflows/node.yml.disabled create mode 100644 .github/workflows/python.yml create mode 100644 .github/workflows/release-controller.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .github/workflows/version-controller.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .pylintrc create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 INSTALL.md create mode 100644 SECURITY.md create mode 100644 VERSIONING.md create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 scripts/bump_year/main.py create mode 100644 scripts/commit_msg_version_bump/main.py create mode 100644 scripts/format_yaml/main.py create mode 100644 scripts/format_yml/main.py create mode 100644 scripts/generate_changelog/main.py create mode 100644 scripts/validate_docker_compose/main.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..9b0c7e2 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,6 @@ +[bumpversion] +current_version = 1.0.0 +commit = True +tag = False + +[bumpversion:file:pyproject.toml] diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..1065bbb --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +#patreon: # Replace with a single Patreon username +#open_collective: # Replace with a single Open Collective username +#ko_fi: # Replace with a single Ko-fi username +#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +#liberapay: # Replace with a single Liberapay username +#issuehunt: # Replace with a single IssueHunt username +#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +#polar: # Replace with a single Polar username +#buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +#thanks_dev: # Replace with a single thanks.dev username +#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9b77ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "" +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] + +**Smartphone (please complete the following information):** + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..43bf04d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +--- +# .github/ISSUE_TEMPLATE/config.yml + +blank_issues_enabled: false +contact_links: + - name: GitHub CICD Template Community Support + url: https://github.com/JuanVilla424/github-cicd-template/discussions + about: Please ask and answer questions here. + - name: GitHub CICD Template Security Reports + url: https://juanvilla424.github.io/github-cicd-template/SECURITY + about: Please report security vulnerabilities here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2bc5d5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: "" +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..82aeaa0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +--- +# .github/dependabot.yml + +version: 2 +updates: + - package-ecosystem: "pip" + directory: "." + schedule: + interval: "weekly" + commit-message: + prefix: "deps" + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..955ad73 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +--- +# .github/workflows/ci.yml + +name: CI + +on: + push: + branches: + - main + +permissions: + contents: read + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m venv venv + source venv/bin/activate + pip install --upgrade pip + pip install poetry + poetry lock + poetry install + - name: Format check with Black + run: | + source venv/bin/activate + black --check scripts/ + - name: Lint with Pylint + run: | + source venv/bin/activate + pylint $(git ls-files '*.py') + - name: Run tests + run: | + source venv/bin/activate + pytest --cov=app --cov-report=xml:coverage.xml || [$? -eq 5] + continue-on-error: true + - name: List files to verify coverage.xml + run: | + ls -la *.xml + cat *.xml + continue-on-error: true + - name: Upload coverage to Codecov + if: success() + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage.xml + flags: unittests + name: codecov-umbrella + continue-on-error: true + - name: OSSF Scorecard action + uses: ossf/scorecard-action@v2.4.0 + with: + results_file: scoreboard-results.json + results_format: json + #publish_results: true + continue-on-error: true + - name: List files to verify scoreboard-results.json + run: | + ls -la scoreboard-results.json + cat scoreboard-results.json + continue-on-error: true + - name: Upload Scorecard Results to Security Scorecards API + if: always() + run: | + curl -X POST \ + "" \ + -H "Authorization: Bearer ${{ secrets.SCORECARD_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d @scoreboard-results.json + continue-on-error: true diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 0000000..e4d17d2 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,18 @@ +--- + +name: Greetings + +on: [pull_request_target, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: "Hooray! That was your first issue, be welcome to pages, thanks for contribute!!" + pr-message: "Mmm, right, may a god can review this one... Lets take a rest, gods working!" diff --git a/.github/workflows/node.yml.disabled b/.github/workflows/node.yml.disabled new file mode 100644 index 0000000..8f98ade --- /dev/null +++ b/.github/workflows/node.yml.disabled @@ -0,0 +1,31 @@ +--- +# .github/workflows/node.yml + +name: Node CI +on: [push] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: ["22.x"] + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Install frontend dependencies + run: | + npm install + npm ci + working-directory: frontend + - name: Format frontend project + run: npm run format + working-directory: frontend + - name: Lint frontend project + run: npm run lint + working-directory: frontend + - name: Build Frontend project + run: npm run build --if-present + working-directory: frontend diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..7da64ac --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,34 @@ +--- +# .github/workflows/python.yml + +name: Python CI +on: [push] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m venv venv + source venv/bin/activate + pip install --upgrade pip + pip install poetry + poetry lock + poetry install + - name: Format check with Black + run: | + source venv/bin/activate + black --check scripts/ + - name: Lint with Pylint + run: | + source venv/bin/activate + pylint $(git ls-files '*.py') + continue-on-error: true diff --git a/.github/workflows/release-controller.yml b/.github/workflows/release-controller.yml new file mode 100644 index 0000000..ab89f77 --- /dev/null +++ b/.github/workflows/release-controller.yml @@ -0,0 +1,77 @@ +--- +# .github/workflows/release-controller.yml + +name: Release Controller + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v*.*.*' + +permissions: + contents: write + id-token: write + +jobs: + release: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + env: + REPO_NAME: ${{ github.event.repository.name }} + # set folder repository var \ + # (settings->security->secrets-variables->actions->variables->repository) \ + # or set default + FOLDER_TO_COMPRESS: 'docs' + steps: + - name: Checkout repository + id: checkout_repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python 3.12 + id: setup_python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + - name: Install dependencies + id: install_dependencies + run: | + python -m venv venv + source venv/bin/activate + pip install --upgrade pip + pip install poetry + poetry lock + poetry install + - name: Package Version + id: package_version + run: | + zip -r "${REPO_NAME}-${GITHUB_REF_NAME}.zip" \ + {INSTALL,SECURITY,README,ICONS,CONTRIBUTING,CODE_OF_CONDUCT}.md \ + requirements.txt .github scripts *adm* smtp-relay elastalert \ + pyproject.toml +# "${FOLDER_TO_COMPRESS}" + env: + REPO_NAME: ${{ env.REPO_NAME }} + GITHUB_REF_NAME: ${{ github.ref_name }} + FOLDER_TO_COMPRESS: ${{ env.FOLDER_TO_COMPRESS }} + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref_name }} + release_name: Release ${{ github.ref_name }} + draft: false + prerelease: false + - name: Upload Release Asset + id: upload_release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: "${{ env.REPO_NAME }}-${{ github.ref_name }}.zip" + asset_name: "${{ env.REPO_NAME }}-${{ github.ref_name }}.zip" + asset_content_type: application/zip diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..995db4e --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,28 @@ +--- +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '23 0 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Mmm, I ll save this one, but no important at moment, thanks!' + stale-pr-message: 'Looks like gods abandoned us... Damn!' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' diff --git a/.github/workflows/version-controller.yml b/.github/workflows/version-controller.yml new file mode 100644 index 0000000..2c8a0d1 --- /dev/null +++ b/.github/workflows/version-controller.yml @@ -0,0 +1,142 @@ +--- +# .github/workflows/version-controller.yml + +name: Version Controller + +on: + push: + branches: + - dev + - test + - prod + - main + +jobs: + version-controller: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + id: checkout_repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python + id: setup_python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install Dependencies + id: install_dependencies + run: | + pip install bump2version toml + - name: Get Current Version + id: get_version + run: | + VERSION=$(python -c "import toml; print(toml.load('pyproject.toml')['tool']['poetry']['version'])") + echo "current_version=$VERSION" >> $GITHUB_OUTPUT + - name: Get Latest Commit Message + id: get_commit + run: | + COMMIT_MESSAGE=$(git log -1 --pretty=%B) + echo "commit_message<> $GITHUB_OUTPUT + echo "$COMMIT_MESSAGE" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Determine Branch + id: determine_branch + run: | + CURRENT_BRANCH=${GITHUB_REF#refs/heads/} + if [ "$CURRENT_BRANCH" = "dev" ]; then + NEXT_BRANCH="test" + elif [ "$CURRENT_BRANCH" = "test" ]; then + NEXT_BRANCH="prod" + elif [ "$CURRENT_BRANCH" = "prod" ]; then + NEXT_BRANCH="main" + else + NEXT_BRANCH="" + fi + echo "next_branch=$NEXT_BRANCH" >> $GITHUB_OUTPUT + echo "current_branch=$CURRENT_BRANCH" >> $GITHUB_OUTPUT + - name: Add Modules + id: add_modules + run: | + rm -rf scripts + git rm --cached scripts + ls -la + git submodule add -b ${{ steps.determine_branch.outputs.current_branch }} https://github.com/JuanVilla424/scripts.git +# - name: Run Changelog Generator +# id: run_changelog +# run: | +# python scripts/generate_changelog/main.py + - name: Check for Forbidden Character + id: check_arrow + run: | + if [[ "${{ steps.get_commit.outputs.commit_message }}" == *"โ†’"* && "${{ steps.get_commit.outputs.commit_message }}" == *"Bump version:"* ]]; then + echo "contains_arrow=true" >> $GITHUB_OUTPUT + else + echo "contains_arrow=false" >> $GITHUB_OUTPUT + fi + - name: Create Tag + id: create_tag + if: steps.check_arrow.outputs.contains_arrow == 'true' + run: | + VERSION=${{ steps.get_version.outputs.current_version }} + BRANCH_NAME=${{ steps.determine_branch.outputs.current_branch }} + TAG_NAME="v${VERSION}-${BRANCH_NAME}" + git tag "$TAG_NAME" + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + continue-on-error: true + - name: Push Tag + id: push_tag + if: steps.check_arrow.outputs.contains_arrow == 'true' + run: | + git push origin "${{ steps.create_tag.outputs.tag_name }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Ensure on Current Branch + id: ensure_branch + if: steps.check_arrow.outputs.contains_arrow == 'true' + run: | + git checkout "${{ steps.determine_branch.outputs.current_branch }}" + - name: Create Pull Request + id: create_pull_request + if: steps.check_arrow.outputs.contains_arrow == 'true' && github.ref != 'refs/heads/main' + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const head = '${{ steps.determine_branch.outputs.current_branch }}'; + const base = '${{ steps.determine_branch.outputs.next_branch }}'; + const title = `from ${{ steps.determine_branch.outputs.current_branch }} - ${{ steps.get_commit.outputs.commit_message }} into ${{ steps.determine_branch.outputs.next_branch }}`; + const body = `Automatically created pull request for release ${{ steps.create_tag.outputs.tag_name }} into ${base} branch.`; + + const { data: existingPRs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + head: head, + base: base, + state: 'open' + }); + + if (existingPRs.length === 0) { + const { data: pr } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + head: head, + base: base, + title: title, + body: body + }); + console.log(`Created PR #${pr.number}: ${pr.html_url}`); + } else { + console.log(`PR already exists: ${existingPRs[0].html_url}`); + } + - name: Push Tag for Main Branch + id: push_tag_to_main + if: github.ref == 'refs/heads/main' && steps.check_arrow.outputs.contains_arrow == 'true' + run: | + VERSION=${{ steps.get_version.outputs.current_version }} + TAG_NAME="v${VERSION}" + git tag "$TAG_NAME" + git push origin "$TAG_NAME" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a8cd55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,134 @@ +# Node.js / npm +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* +package-lock.json* + +# TypeScript / TSX +dist/ +*.tsbuildinfo + +# Poetry +poetry.lock + +# Environment variables +.env +.env.*.local + +# Logs +logs +*.log +pnpm-debug.log* +lerna-debug.log* + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Output of 'npm pack' +*.tgz + +# Lockfiles +yarn.lock +.pnpm-lock.yaml + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Coverage directory used by tools like +instanbul/ +istanbul/jest +jest/ +coverage/ + +# Output of 'tsc' command +out/ +build/ +tmp/ +temp/ + +# Python +__pycache__/ +*.py[cod] +*.so +*.egg +*.egg-info/ +pip-wheel-metadata/ +*.pyo +*.pyd +*.whl +*.pytest_cache/ +.tox/ +env/ +venv +venv/ +ENV/ +env.bak/ +.venv/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Coverage reports +htmlcov/ +.coverage +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover + +# Jupyter Notebook +.ipynb_checkpoints + +# Django stuff: +staticfiles/ +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# VS Code settings +.vscode/ +.idea/ + +# macOS files +.DS_Store +.AppleDouble +.LSOverride + +# Windows files +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Linux system files +*.swp +*~ + +# IDE specific +*.iml +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ca644ce --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,83 @@ +--- +# .pre-commit-config.yaml + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + types: [yaml] + files: \.(yaml)$ + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + language_version: python3 + - repo: https://github.com/PyCQA/pylint + rev: v3.3.1 + hooks: + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + args: [--rcfile=.pylintrc, .] + - repo: https://github.com/hadolint/hadolint + rev: v2.13.1-beta + hooks: + - id: hadolint + name: hadolint + entry: hadolint + language: system + types: [dockerfile] + files: ^Dockerfile$ + - repo: local + hooks: + - id: yaml-format + name: yaml-format + entry: python scripts/format_yaml/main.py + language: system + types: [yaml] + files: \.(yaml)$ + exclude: '.github/.*' + - id: yamllint + name: yamllint + entry: yamllint + language: system + types: [yaml] + # files: \.(yml|yaml)$ + files: \.(yaml)$ + exclude: '.github/.*' + # # TODO: Solve error + # - id: yml-format + # name: yml-format + # entry: python scripts/format_yml/main.py + # language: system + # types: [yaml] + # files: \.(yml)$ + # exclude: '.github/.*' + - id: docker-compose + name: docker-compose + entry: python scripts/validate_docker_compose/main.py + language: system + types: [yaml] + files: ^docker-compose(\.dev|\.prod)?\.yml$ + - id: commit-msg-version-check + name: commit-msg-version-check + entry: python scripts/commit_msg_version_bump/main.py + always_run: true + language: system + args: [.git/COMMIT_EDITMSG] + stages: [pre-push] + - id: generate-changelog + name: generate-changelog + entry: python scripts/generate_changelog/main.py + always_run: true + language: system + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + files: \.(markdown|md)$ diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..be04b8d --- /dev/null +++ b/.pylintrc @@ -0,0 +1,652 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=venv,node_modules,scripts + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Resolve imports to .pyi stubs if available. May reduce no-member messages and +# increase not-an-iterable messages. +prefer-stubs=no + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.12 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of positional arguments for function / method. +max-positional-arguments=5 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=2500 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=missing-module-docstring, + invalid-name, + too-few-public-methods, + E1101, + C0115, + duplicate-code, + raise-missing-from, + wrong-import-order, + ungrouped-imports, + reimported, + too-many-locals, + missing-timeout, + broad-exception-caught, + broad-exception-raised, + line-too-long + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +#enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +# Let 'consider-using-join' be raised when the separator to join on would be +# non-empty (resulting in expected fixes of the type: ``"- " + " - +# ".join(items)``) +suggest-join-with-non-empty-separator=yes + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..785c7a9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +## [1.0.0] - 2024-10-23 + +### Other Changes + +- rebased scripts +- feat: added scripts +- Initial commit + +## [1.0.0] - 2024-10-23 + +### Other Changes + +- rebased scripts +- feat: added scripts +- Initial commit diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..6e9f323 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,40 @@ +# ๐Ÿ“ Code of Conduct + +Take Note About This... **Take Note...** + +## ๐Ÿค Our Commitment + +We are committed to fostering an open and welcoming environment for all contributors. As such, everyone who participates in **GitHub CICD Template** is expected to adhere to the following code of conduct. + +## ๐ŸŒŸ Expected Behavior + +- **Respect:** Be respectful of differing viewpoints and experiences. +- **Constructive Feedback:** Provide constructive feedback and be open to receiving it. +- **Empathy and Kindness:** Show empathy and kindness towards other contributors. +- **Respect for Maintainers:** Respect the decisions of the maintainers. +- **Positive Intent:** Assume positive intent in interactions with others. + +## ๐Ÿšซ Unacceptable Behavior + +- **Harassment or Discrimination:** Harassment or discrimination in any form. +- **Inappropriate Language or Imagery:** Use of inappropriate language or imagery. +- **Personal Attacks:** Personal attacks or insults. +- **Public or Private Harassment:** Public or private harassment. + +## ๐Ÿ“ข Reporting Misconduct + +If you encounter any behavior that violates this code of conduct, please report it by contacting [email@dominio.com](mailto:email@dominio.com). All complaints will be reviewed and handled appropriately. + +## โš–๏ธ Enforcement + +Instances of unacceptable behavior may be addressed by the project maintainers, who are responsible for clarifying and enforcing this code of conduct. Violations may result in temporary or permanent bans from the project and related spaces. + +## ๐Ÿ™ Acknowledgments + +This code of conduct is adapted from the [Contributor Covenant, version 2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). + +--- + +## ๐Ÿ“œ License + +2024 - This project is licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html). You are free to use, modify, and distribute this software under the terms of the GPL-3.0 license. For more details, please refer to the [LICENSE](LICENSE) file included in this repository. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a2ac4a0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# ๐Ÿค Contributing to github-cicd-template + +We welcome contributions to github-cicd-template! To make sure the process goes smoothly, please follow these guidelines: + +## ๐Ÿ“‹ Code of Conduct + +Please note that all participants in our project are expected to follow our [Code of Conduct](#-code-of-conduct). Make sure to review it before contributing. + +## ๐Ÿ›  How to Contribute + +1. **Fork the repository**: + Fork the project to your GitHub account using the GitHub interface. + +2. **Create a new branch**: + Use a descriptive branch name for your feature or bugfix: + + git checkout -b feature/your-feature-name + +3. **Make your changes**: + Implement your feature or fix the bug in your branch. Make sure to include tests where applicable and follow coding standards. + +4. **Test your changes**: + Run the test suite to ensure your changes donโ€™t break any functionality: + + docker-compose exec backend pytest # For backend tests + docker-compose exec frontend npm test # For frontend tests + +5. **Commit your changes**: + Use meaningful commit messages that explain what you have done: + + git commit -m "Add feature/fix: Description of changes" + +6. **Push your changes**: + Push your changes to your fork: + + git push origin feature/your-feature-name + +7. **Submit a Pull Request**: + Create a pull request on the main repository, detailing the changes youโ€™ve made. Link any issues your changes resolve and provide context. + +## ๐Ÿ“‘ Guidelines for Contributions + +- **Lint your code** before submitting a pull request. We use [ESLint](https://eslint.org/) for frontend and [pylint](https://www.pylint.org/) for backend linting. +- Ensure **test coverage** for your code. Uncovered code may delay the approval process. +- Write clear, concise **commit messages**. + +Thank you for helping improve! + +--- + +## ๐Ÿ“œ License + +2024 - This project is licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html). You are free to use, modify, and distribute this software under the terms of the GPL-3.0 license. For more details, please refer to the [LICENSE](LICENSE) file included in this repository. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..e69de29 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6cccee6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,51 @@ +# ๐Ÿ”’ Security Policy + +Security Policy for **SMTP Relay Docker** repository includes all required compliance matrix and artifact mapping. + +## ๐Ÿงฎ Supported Versions + +We provide security updates for the following versions of our project: + +| Version | Status | Secure | +| -------- | ----------- | ------ | +| 2023.x.x | End-of-life | No | +| 2024.7.x | End-of-life | Yes | +| 2024.8.x | Supported | Yes | + +## ๐Ÿ›ก๏ธ Security Practices + +- We follow best practices for secure coding and infrastructure management. +- Regular security audits and code reviews are conducted to identify and mitigate potential risks. +- Dependencies are monitored and updated to address known vulnerabilities. + +## ๐Ÿ“ฒ Security Updates + +- Security updates are released as soon as possible after a vulnerability is confirmed. +- Users are encouraged to update to the latest version to benefit from security fixes. + +## ๐Ÿšจ Reporting a Vulnerability + +If you discover a security vulnerability within this project, please follow these steps: + +1. **Do not create a public issue.** Instead, contact us directly to responsibly disclose the vulnerability. + +2. **Email** [juan.villa@quipux.com](mailto:juan.villa@quipux.com) with the following information: + + - A description of the vulnerability. + - Steps to reproduce the issue. + - Any potential impact or severity. + +3. **Wait for a response.** We will acknowledge your report and work with you to address the issue promptly. + +## ๐Ÿ›ฐ๏ธ Additional Resources + +- [OWASP Security Guidelines](https://owasp.org/) +- [AWS Security Best Practices](https://aws.amazon.com/security/security-best-practices/) + +Thank you for helping us keep this project secure! + +--- + +## ๐Ÿ“œ License + +2024 - This project is licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html). You are free to use, modify, and distribute this software under the terms of the GPL-3.0 license. For more details, please refer to the [LICENSE](LICENSE) file included in this repository. diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 0000000..95988ff --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,92 @@ +# ๐Ÿ“ฆ Versioning Strategy + +We follow [Semantic Versioning](https://semver.org/) with the version format `MAJOR.MINOR.PATCH`. This approach helps in managing releases and ensuring compatibility across different versions of our project. + +## ๐Ÿงฉ Version Components + +- **MAJOR:** Increments when you make incompatible API changes. +- **MINOR:** Increments when you add functionality in a backward-compatible manner. +- **PATCH:** Increments when you make backward-compatible bug fixes. + +## ๐ŸŒฟ Branch Workflow + +Our repository uses the following branches to manage the development and release process: + +1. **dev:** Active development happens here. New features and bug fixes are merged into this branch. +2. **test:** Once features in `dev` are stable, they are promoted to `test` for further testing. +3. **prod:** After thorough testing, changes from `test` are promoted to `prod` for production deployment. +4. **main:** The `main` branch holds the latest production-ready code. Releases are tagged from this branch. + +## ๐Ÿ“ Commit Message Guidelines + +To automate versioning and changelog generation, it's essential to follow specific commit message conventions. Each commit message should include one of the following [commit types](#-commit-types) and an optional scope. Additionally, include one of the [versioning keywords](#-versioning-keywords) to indicate the type of version bump. + +### ๐Ÿ—‚๏ธ Commit Message Structure + +```bash +(): [versioning keyword] +``` + +Example: + +```bash +feat(authentication): add OAuth2 support [minor candidate] +``` + +### ๐Ÿ”ค Commit Types + +- **feat**: A new feature for the user. +- **fix**: A bug fix for the user. +- **docs**: Documentation only changes. +- **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc.). +- **refactor**: A code change that neither fixes a bug nor adds a feature. +- **perf**: A code change that improves performance. +- **test**: Adding missing tests or correcting existing tests. +- **chore**: Changes to the build process or auxiliary tools and libraries. + +### ๐Ÿ”‘ Versioning Keywords + +**Optionally** Include one of the following keywords at the end of the commit message to indicate the type of version bump: + +- **[major candidate]** Indicates that this commit should trigger a MAJOR version bump. +- **[minor candidate]** Indicates that this commit should trigger a MINOR version bump. +- **[patch candidate]** Indicates that this commit should trigger a PATCH version bump. + +Examples: + +- `feat(authentication): add OAuth2 support [minor candidate]` +- `fix(api): resolve authentication error [patch candidate]` +- `refactor(core): restructure authentication module [major candidate]` + +## ๐Ÿš€ Release Process + +1. Development Phase: + + - Developers work on features and bug fixes in the `dev` branch. + - Commit messages should follow the guidelines above to indicate the type of changes. + +2. Testing Phase: + Once a set of features is complete and stable in `dev`, they are promoted to the `test` branch. + Automated workflows will handle version bumps and pull requests to the next branch. + +3. Production Phase: + After successful testing in `test`, changes are promoted to the `prod` branch. + Final testing and validation occur here before deployment. + +4. Release: + + - Changes from `prod` are merged into `main`. + - A release is tagged, and the `CHANGELOG.md` is updated automatically based on commit messages. + +## โญ Best Practices + +- **Atomic Commits**: Ensure each commit represents a single logical change. +- **Descriptive Messages**: Write clear and concise commit messages that accurately describe the changes. +- **Consistent Format**: Adhere strictly to the commit message structure to enable automation tools to function correctly. +- **Avoid Force Pushes**: Respect branch protection rules to maintain repository integrity. + +--- + +## ๐Ÿ“œ License + +2024 - This project is licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html). You are free to use, modify, and distribute this software under the terms of the GPL-3.0 license. For more details, please refer to the [LICENSE](LICENSE) file included in this repository. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2f1231f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,71 @@ +[tool.poetry] +name = "scripts" +version = "1.0.0" +description = "CICD Core Scripts" +authors = ["B "] +license = "Apache 2.0" +readme = "README.md" +package-mode = false + +[tool.poetry.dependencies] +python = "^3.12" +setuptools = "^75.2.0" +idna="^3.0" +certifi="^2024.8.30" +bump2version="^1.0.0" + +[tool.poetry.group.dev.dependencies] +pre-commit = "^4.0.0" +pylint="^3.3.0" +yamllint="^1.35.0" +isort = "^5.12.0" +toml="^0.10.0" +black="^24.3.0" +pytest = "^8.3.1" +httpx = { version = ">=0.24.0", optional = true } +pytest-cov = { version = "^5.0.0", optional = true } +coverage = "^7.2.5" + +[tool.poetry.extras] +migrations = ["alembic"] +testing = ["pytest", "httpx"] + +[tool.black] +line-length = 100 +target-version = ['py312'] +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | node_modules +)/ +''' + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "--cov=app --cov-report=xml:coverage.xml --cov-report=term" +testpaths = ["tests"] + +[tool.isort] +profile = "black" +line_length = 100 +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true + +[tool.pylint] +rcfile = ".pylintrc" + +[build-system] +requires = ["poetry-core>=1.0.4"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cd9baac --- /dev/null +++ b/requirements.txt @@ -0,0 +1,27 @@ +# Pre-Commit for pre-commit hooks management +pre-commit>=4.0.0 + +# Pylint for python lint +pylint>=3.3.1 + +# YamlLint for YAML lint +yamllint>=1.35.1 + +# Poetry for package management alternative +poetry>=1.8.4 + +# Bump2Version for version release control +bump2version>=1.0.0 + +# Toml for pyproject.toml management +toml>=0.10.1 + +# Black for Code Formatting +black>=24.3.0 + +# Pytest for Unit/Integration Testing +pytest>=7.3.1 +pytest-cov>=5.0.0 + +# Coverage Reporting +coverage>=7.2.5 diff --git a/scripts/bump_year/main.py b/scripts/bump_year/main.py new file mode 100644 index 0000000..1138b1a --- /dev/null +++ b/scripts/bump_year/main.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +bump_year.py + +A script to bump the year part of the version in pyproject.toml. +Resets major and minor versions to 0 when the year is incremented. + +Usage: + bump_year.py +""" + +import datetime +import toml +import sys + + +def bump_year() -> None: + """ + Bumps the year in pyproject.toml and resets major and minor versions to 0. + """ + current_year = datetime.datetime.now().year + pyproject_path = "pyproject.toml" + + try: + with open(pyproject_path, "r", encoding="utf-8") as file: + data = toml.load(file) + except FileNotFoundError: + print(f"Error: {pyproject_path} not found.") + sys.exit(1) + except toml.TomlDecodeError: + print(f"Error: Failed to parse {pyproject_path}.") + sys.exit(1) + + try: + version = data["tool"]["poetry"]["version"] + year, major, minor = version.split(".") + except (KeyError, ValueError): + print("Error: Version format is incorrect in pyproject.toml.") + sys.exit(1) + + if int(year) < current_year: + print(f"Updating year from {year} to {current_year}") + year = str(current_year) + major = "0" + minor = "0" + new_version = f"{year}.{major}.{minor}" + data["tool"]["poetry"]["version"] = new_version + try: + with open(pyproject_path, "w", encoding="utf-8") as file: + toml.dump(data, file) + print(f"Year bumped to {new_version}") + except Exception as e: + print(f"Error writing to {pyproject_path}: {e}") + sys.exit(1) + else: + print("Year is up-to-date. No need to bump.") + + +def main() -> None: + """ + Main function to execute the year bumping process. + """ + bump_year() + + +if __name__ == "__main__": + main() diff --git a/scripts/commit_msg_version_bump/main.py b/scripts/commit_msg_version_bump/main.py new file mode 100644 index 0000000..053e541 --- /dev/null +++ b/scripts/commit_msg_version_bump/main.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +commit_msg_version_bump.py + +A script to bump the version in pyproject.toml based on commit message keywords. +Handles major, minor, and patch releases. + +Usage: + commit_msg_version_bump.py +""" + +import sys +import re +import subprocess +import toml + +DEBUG = False + + +def bump_version(part: str) -> None: + """ + Bumps the specified part of the version using bump2version and commits the change. + + Args: + part (str): The part of the version to bump ('major', 'minor', 'patch'). + + Raises: + subprocess.CalledProcessError: If bump2version or git commands fail. + """ + try: + subprocess.run(["bump2version", part], check=True) + print(f"Successfully bumped the {part} version.") + except subprocess.CalledProcessError: + print(f"Failed to bump the {part} version.") + sys.exit(1) + + # Retrieve the new version from pyproject.toml + new_version = get_new_version() + + if DEBUG: + print(f"Target version {new_version}") + + # Stage the changed pyproject.toml + try: + subprocess.run(["git", "add", "pyproject.toml"], check=True) + except subprocess.CalledProcessError: + print("Failed to stage pyproject.toml.") + sys.exit(1) + + # Commit the change + try: + subprocess.run(["git", "commit", "-m", f"Bump {part} version to {new_version}"], check=True) + print(f"Committed the bumped {part} version to {new_version}.") + except subprocess.CalledProcessError: + print(f"Failed to commit the bumped {part} version.") + sys.exit(1) + + +def get_new_version() -> str: + """ + Retrieves the new version from pyproject.toml. + + Returns: + str: The new version string. + + Raises: + SystemExit: If the version cannot be retrieved. + """ + pyproject_path = "pyproject.toml" + try: + with open(pyproject_path, "r", encoding="utf-8") as file: + data = toml.load(file) + version = data["tool"]["poetry"]["version"] + return version + except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError): + print(f"Error: Unable to retrieve the version from {pyproject_path}.") + sys.exit(1) + + +def main() -> None: + """ + Main function to parse the commit message and perform version bumping. + """ + if DEBUG: + print(f"Sys: {sys}") + + if len(sys.argv) < 2: + print("Usage: commit_msg_version_bump.py ") + sys.exit(1) + + commit_msg_file = sys.argv[1] + + try: + with open(commit_msg_file, "r", encoding="utf-8") as file: + commit_msg = file.read().strip() + except FileNotFoundError: + print(f"Commit message file not found: {commit_msg_file}") + sys.exit(1) + + if DEBUG: + print(f"Commit message file: {commit_msg_file}") + print(f"commit_msg: {commit_msg}") + + # Define patterns for candidate types + major_pattern = re.compile(r"\bmajor candidate\b", re.IGNORECASE) + minor_pattern = re.compile(r"\bminor candidate\b", re.IGNORECASE) + patch_pattern = re.compile(r"\bpatch candidate\b", re.IGNORECASE) + + if major_pattern.search(commit_msg): + print("Major candidate release detected. Bumping major version...") + bump_version("major") + elif minor_pattern.search(commit_msg): + print("Minor candidate release detected. Bumping minor version...") + bump_version("minor") + elif patch_pattern.search(commit_msg): + print("Patch candidate release detected. Bumping patch version...") + bump_version("patch") + else: + print("No version bump detected in commit message.") + + +if __name__ == "__main__": + main() diff --git a/scripts/format_yaml/main.py b/scripts/format_yaml/main.py new file mode 100644 index 0000000..5860645 --- /dev/null +++ b/scripts/format_yaml/main.py @@ -0,0 +1,101 @@ +# scripts/validate_docker_compose.py +import re +import sys +import os + +DEBUG = False + + +def format_yaml_file(file_path): + """ + Format a yaml file + + Args: + :param file_path: + """ + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + lines = content.splitlines() + + if not lines or not lines[0].strip().startswith("---"): + lines.insert(0, "---") + print(f"[FORMAT] Added '---' at the beginning of {file_path}.") + + for i, line in enumerate(lines): + original_line = line + line = re.sub(r"\[\s+", "[", line) + line = re.sub(r"\s+]", "]", line) + line = re.sub(r"\(\s+", "(", line) + line = re.sub(r"\s+\)", ")", line) + if line != original_line: + lines[i] = line + print(f"[FORMAT] Removed extra spaces inside brackets in line {i+1} of {file_path}.") + + inside_run_block = False + new_lines = [] + for i, line in enumerate(lines): + if re.match(r"^run:\s*\|", line): + inside_run_block = True + new_lines.append(line) + continue + if inside_run_block and re.match(r"^\S", line): + inside_run_block = False + + if inside_run_block and len(line) > 120: + split_pos = line.rfind(" ", 0, 120) + if split_pos != -1: + split_line1 = line[:split_pos] + split_line2 = " " + line[split_pos + 1 :].lstrip() + new_lines.append(split_line1) + new_lines.append(split_line2) + print(f"[FORMAT] Split long line in 'run' block at line {i+1} of {file_path}.") + continue + if not split_pos != -1: + split_line1 = line[:120] + split_line2 = " " + line[120:].lstrip() + new_lines.append(split_line1) + new_lines.append(split_line2) + print(f"[FORMAT] Split long line in 'run' block at line {i+1} of {file_path}.") + continue + + new_lines.append(line) + + formatted_content = "\n".join(lines) + "\n" + + with open(file_path, "w", newline="\n", encoding="utf-8") as f: + f.write(formatted_content) + + print(f"[FORMAT] Formatted {file_path} with LF line endings.") + sys.exit(0) + + +def main(): + """Main void function to format yaml files.""" + + if len(sys.argv) < 2: + print("[ERROR] Incorrect usage. Must specify at least one file.") + sys.exit(1) + + files_to_validate = sys.argv[1:] + for file in files_to_validate: + file_path = os.path.abspath(file) + if os.path.isfile(file_path): + format_yaml_file(file_path) + else: + print(f"[ERROR] File {file_path} does not exist.") + sys.exit(1) + + print("[OK] All checks passed successfully.") + sys.exit(0) + + +if __name__ == "__main__": + if sys.stdout.encoding.lower() != "utf-8": + try: + sys.stdout.reconfigure(encoding="utf-8") + except AttributeError: + import io + + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + main() diff --git a/scripts/format_yml/main.py b/scripts/format_yml/main.py new file mode 100644 index 0000000..417d169 --- /dev/null +++ b/scripts/format_yml/main.py @@ -0,0 +1,88 @@ +# scripts/validate_docker_compose.py +import re +import sys +import os + +DEBUG = False + + +def format_yml_file(file_path): + """ + Function to format yml files + + Args: + file_path (str): + """ + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + lines = content.splitlines() + new_lines = [] + + if not lines or not lines[0].strip().startswith("---"): + lines.insert(0, "---") + print(f"[FORMAT] Added '---' at the beginning of {file_path}.") + + for i, line in enumerate(lines): + original_line = line + line = re.sub(r"\[\s+", "[", line) + line = re.sub(r"\s+]", "]", line) + line = re.sub(r"\(\s+", "(", line) + line = re.sub(r"\s+\)", ")", line) + if line != original_line: + lines[i] = line + print(f"[FORMAT] Removed extra spaces inside brackets in line {i+1} of {file_path}.") + + for i, line in enumerate(lines, start=1): + current_indent = len(line) - len(line.lstrip(" ")) + + while len(line) > 120: + split_pos = line.rfind(" ", 0, 120) + if split_pos != -1: + split_line1 = line[:split_pos] + " \\" + split_line2 = " " * current_indent + " " + line[split_pos + 1 :].lstrip() + new_lines.append(split_line1) + new_lines.append(split_line2) + print(f"[FORMAT] Split long line at line {i} in {file_path}.") + continue + if not split_pos != -1: + split_line1 = line[:120] + " \\" + split_line2 = " " * current_indent + " " + line[120 + 1 :].lstrip() + new_lines.append(split_line1) + new_lines.append(split_line2) + print(f"[FORMAT] Force split long line at line {i} in {file_path}.") + continue + + new_lines.append(line) + + formatted_content = "\n".join(new_lines) + "\n" + + with open(file_path, "w", newline="\n", encoding="utf-8") as f: + f.write(formatted_content) + + print(f"[FORMAT] Formatted {file_path} with LF line endings.") + sys.exit(0) + + +def main(): + """Main void function to format yml files.""" + + if len(sys.argv) < 2: + print("[ERROR] Incorrect usage. Must specify at least one file.") + sys.exit(1) + + files_to_validate = sys.argv[1:] + for file in files_to_validate: + file_path = os.path.abspath(file) + if os.path.isfile(file_path): + format_yml_file(file_path) + else: + print(f"[ERROR] File {file_path} does not exist.") + sys.exit(1) + + print("[OK] All checks passed successfully.") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_changelog/main.py b/scripts/generate_changelog/main.py new file mode 100644 index 0000000..0777dfa --- /dev/null +++ b/scripts/generate_changelog/main.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +""" +generate_changelog.py + +This script automatically generates or updates the CHANGELOG.md file based on commit messages. +It processes all commit history and categorizes commits into features, fixes, etc., while +grouping non-conforming commits under a separate section. + +Usage: + python generate_changelog.py +""" + +import subprocess +import re +from datetime import datetime, timezone +from typing import List, Dict, Tuple + +DEBUG = False + +# Define the path to the CHANGELOG.md +CHANGELOG_PATH = "CHANGELOG.md" + +# Define the commit message regex pattern +# Example: feat(authentication): add OAuth2 support [minor candidate] +COMMIT_REGEX = re.compile( + r"^(?Pfeat|fix|docs|style|refactor|perf|test|chore)" + r"(?:\((?P[^)]+)\))?:\s+(?P.+?)\s+\[(?Pminor candidate|major " + r"candidate|patch candidate)]$", + re.IGNORECASE, +) + +# Mapping of commit types to changelog sections +TYPE_MAPPING = { + "feat": "### Features", + "fix": "### Bug Fixes", + "docs": "### Documentation", + "style": "### Styles", + "refactor": "### Refactors", + "perf": "### Performance Improvements", + "test": "### Tests", + "chore": "### Chores", +} + + +def get_latest_version_from_changelog() -> str: + """ + Retrieves the latest version from the CHANGELOG.md file. + + Returns: + str: The latest version number or an empty string if not found. + """ + try: + with open(CHANGELOG_PATH, "r", encoding="utf-8") as f: + for line in f: + match = re.match(r"^## \[(\d+\.\d+\.\d+)]", line) + if match: + return match.group(1) + except FileNotFoundError: + return "" + return "" + + +def get_commits_since_version(version: str) -> List[str]: + """ + Retrieves commit messages since the specified version. + + Args: + version (str): The version number to retrieve commits since. + + Returns: + List[str]: A list of commit messages. + """ + try: + if version: + commits = ( + subprocess.check_output(["git", "log", f"v{version}..HEAD", "--pretty=format:%s"]) + .decode() + .split("\n") + ) + else: + # If no version found in CHANGELOG, get all commits + commits = ( + subprocess.check_output(["git", "log", "--pretty=format:%s"]).decode().split("\n") + ) + return commits + except subprocess.CalledProcessError: + return [] + + +def parse_commits(commits: List[str]) -> Tuple[Dict[str, List[str]], List[str]]: + """ + Parses commit messages and categorizes them based on type. + + Args: + commits (List[str]): A list of commit messages. + + Returns: + Tuple[Dict[str, List[str]], List[str]]: A dictionary categorizing commits and a list of non-conforming commits. + """ + changelog = {section: [] for section in TYPE_MAPPING.values()} + non_conforming_commits = [] + + for commit in commits: + match = COMMIT_REGEX.match(commit) + if match: + commit_type = match.group("type").lower() + scope = match.group("scope") + description = match.group("description").strip() + versioning_keyword = match.group("versioning_keyword").lower() + + section = TYPE_MAPPING.get(commit_type) + if section: + if scope: + entry = f"- **{scope}**: {description} (`{versioning_keyword}`)" + else: + entry = f"- {description} (`{versioning_keyword}`)" + changelog[section].append(entry) + else: + non_conforming_commits.append(commit) + else: + non_conforming_commits.append(commit) + + # Remove empty sections + changelog = {k: v for k, v in changelog.items() if v} + return changelog, non_conforming_commits + + +def generate_changelog_entry( + version: str, changelog: Dict[str, List[str]], non_conforming: List[str] +) -> str: + """ + Generates a changelog entry for a specific version. + + Args: + version (str): The version number. + changelog (Dict[str, List[str]]): The categorized changelog entries. + non_conforming (List[str]): List of non-conforming commit messages. + + Returns: + str: The formatted changelog entry. + """ + date = datetime.now(timezone.utc).strftime("%Y-%m-%d") + entry = f"## [{version}] - {date}\n\n" + for section, items in changelog.items(): + entry += f"{section}\n" + for item in items: + entry += f"{item}\n" + entry += "\n" + + if non_conforming: + entry += "### Other Changes\n" + for commit in non_conforming: + entry += f"- {commit}\n" + entry += "\n" + + return entry + + +def update_changelog(version: str, new_entry: str): + """ + Updates the CHANGELOG.md file by prepending the new entry. + + Args: + version (str): The version number. + new_entry (str): The new changelog entry to add. + """ + if DEBUG: + print(f"Updating version... {version}") + try: + with open(CHANGELOG_PATH, "r", encoding="utf-8") as f: + existing_content = f.read() + except FileNotFoundError: + existing_content = "" + + with open(CHANGELOG_PATH, "w", encoding="utf-8") as f: + f.write(new_entry + "\n" + existing_content) + + +def get_next_version(latest_version: str, version_bump: str) -> str: + """ + Calculates the next version based on the current version and the type of version bump. + + Args: + latest_version (str): The latest version number. + version_bump (str): The type of version bump ('major', 'minor', 'patch'). + + Returns: + str: The next version string. + """ + if not latest_version: + # Default initial version if no changelog exists + return "1.0.0" + + major, minor, patch = map(int, latest_version.split(".")) + + if version_bump == "major": + major += 1 + minor = 0 + patch = 0 + elif version_bump == "minor": + minor += 1 + patch = 0 + elif version_bump == "patch": + patch += 1 + + return f"{major}.{minor}.{patch}" + + +def get_version_bump(commits: List[str]) -> str: + """ + Determines the type of version bump based on commit messages. + + Args: + commits (List[str]): A list of commit messages. + + Returns: + str: The type of version bump ('major', 'minor', 'patch') or an empty string if none. + """ + # Priority: major > minor > patch + bump = "" + + for commit in commits: + match = COMMIT_REGEX.match(commit) + if match: + keyword = match.group("versioning_keyword").lower() + if keyword == "major candidate": + bump = "major" + elif keyword == "minor candidate" and bump != "major": + bump = "minor" + elif keyword == "patch candidate" and not bump: + bump = "patch" + + return bump + + +def main(): + """ + Main function to generate or update the CHANGELOG.md. + """ + latest_version = get_latest_version_from_changelog() + print(f"Latest version in CHANGELOG.md: {latest_version}") + commits = get_commits_since_version(latest_version) + if not commits: + print("No new commits to include in the changelog.") + return + + changelog, non_conforming = parse_commits(commits) + if not changelog and not non_conforming: + print("No valid commits found for changelog generation.") + return + + # Determine the next version based on the highest priority keyword + version_bump = get_version_bump(commits) + + if not version_bump and non_conforming: + # Assign a patch bump if there are non-conforming commits but no version bump keywords + version_bump = "patch" + + if not version_bump and not non_conforming: + print("No versioning keyword found in commits.") + return + + # Get the next version + next_version = get_next_version(latest_version, version_bump) + print(f"Bumping version: {version_bump} to {next_version}") + + # Generate changelog entry + changelog_entry = generate_changelog_entry(next_version, changelog, non_conforming) + + # Update CHANGELOG.md + update_changelog(next_version, changelog_entry) + print(f"CHANGELOG.md updated with version {next_version}.") + + +if __name__ == "__main__": + main() diff --git a/scripts/validate_docker_compose/main.py b/scripts/validate_docker_compose/main.py new file mode 100644 index 0000000..0cacdbb --- /dev/null +++ b/scripts/validate_docker_compose/main.py @@ -0,0 +1,90 @@ +# scripts/validate_docker_compose.py + +import subprocess +import sys +import os + +DEBUG = False + + +def format_docker_compose(file_path): + """ + Format a docker compose file + + Args: + :param file_path: + """ + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + + lines = content.splitlines() + + if not lines or not lines[0].strip().startswith("---"): + lines.insert(0, "---") + print(f"[FORMAT] Added '---' at the beginning of {file_path}.") + + formatted_content = "\n".join(lines) + "\n" + + with open(file_path, "w", newline="\n", encoding="utf-8") as f: + f.write(formatted_content) + + print(f"[FORMAT] Formatted {file_path} with LF line endings.") + + +def validate_docker_compose(file_path): + """ + Validate a Docker compose file. + + Args: + :param file_path: + """ + try: + result = subprocess.run( + ["docker-compose", "-f", file_path, "config"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + print(f"[OK] {file_path} is valid.") + if DEBUG: + print(f"[DEBUG] Result response: {result}") + except subprocess.CalledProcessError as e: + print(f"[ERROR] Error in file {file_path}:") + print(e.stderr) + sys.exit(1) + except FileNotFoundError: + print("[ERROR] docker-compose not found.") + sys.exit(1) + + +def main(): + """Main void function for validating Docker compose files.""" + + if len(sys.argv) < 2: + print("[ERROR] Incorrect usage. Must specify at least one file.") + sys.exit(1) + + files_to_validate = sys.argv[1:] + for file in files_to_validate: + file_path = os.path.abspath(file) + if os.path.isfile(file_path): + format_docker_compose(file_path) + validate_docker_compose(file_path) + else: + print(f"[ERROR] File {file_path} does not exist.") + sys.exit(1) + + print("[OK] All checks passed successfully.") + sys.exit(0) + + +if __name__ == "__main__": + if sys.stdout.encoding.lower() != "utf-8": + try: + sys.stdout.reconfigure(encoding="utf-8") + except AttributeError: + import io + + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + main()