From 123ffb03227642a84f1d6cdc04b8e35999ce6e10 Mon Sep 17 00:00:00 2001 From: Nautobot-Bot <79372327+nautobot-bot@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:56:14 -0600 Subject: [PATCH] Cookie updated by NetworkToCode Cookie Drift Manager Tool (#84) * Cookie updated by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "dir": "nautobot-app", "ref": "refs/tags/nautobot-app-v2.1.0", "path": null } ``` Cookie: ``` { "remote": "https://github.com/nautobot/nautobot-app-capacity-metrics.git", "path": "/tmp/tmp7__nye6a/nautobot-app-capacity-metrics", "repository_path": "/tmp/tmp7__nye6a/nautobot-app-capacity-metrics", "dir": "", "branch_prefix": "drift-manager", "context": { "codeowner_github_usernames": "@nautobot/po-tl-telemetry", "full_name": "Network to Code, LLC", "email": "opensource@networktocode.com", "github_org": "nautobot", "app_name": "nautobot_capacity_metrics", "verbose_name": "Metrics & Monitoring Extension App", "app_slug": "nautobot-capacity-metrics", "project_slug": "nautobot-app-capacity-metrics", "repo_url": "https://github.com/nautobot/nautobot-app-capacity-metrics", "base_url": "capacity-metrics", "min_nautobot_version": "2.0.0", "max_nautobot_version": "2.9999", "camel_name": "NautobotCapacityMetrics", "project_short_description": "App to improve the instrumentation of Nautobot and expose additional metrics (Application Metrics, RQ Worker).", "model_class_name": "None", "open_source_license": "Apache-2.0", "docs_base_url": "https://docs.nautobot.com", "docs_app_url": "https://docs.nautobot.com/projects/capacity-metrics/en/latest", "_template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "_output_dir": "/tmp/tmp7__nye6a", "_repo_dir": "/github/home/.cookiecutters/cookiecutter-nautobot-app/nautobot-app", "_checkout": "refs/tags/nautobot-app-v2.1.0" }, "base_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.CREATE", "post_actions": [ "PostAction.BLACK" ], "baked_commit_ref": "e7aa0f1fcbb2737f76621c0e2a80bb6e32c0429d", "draft": true } ``` CLI Arguments: ``` { "cookie_dir": "", "input": false, "json_filename": "", "output_dir": "", "push": true, "template": "", "template_dir": "", "template_ref": "refs/tags/nautobot-app-v2.1.0", "pull_request": null, "post_action": [], "disable_post_actions": false, "draft": null } ``` * chore: Manual fixes * chore Poetry lock * chore: Changelog fragment --------- Co-authored-by: bakebot Co-authored-by: Jan Snasel --- .cookiecutter.json | 4 +- .../pull_request_template.md | 2 +- .github/workflows/ci.yml | 57 +++- .github/workflows/rebake.yml | 118 -------- LICENSE | 2 +- changes/.gitignore | 1 + changes/84.changed | 1 + development/Dockerfile | 4 +- development/docker-compose.base.yml | 6 +- development/docker-compose.dev.yml | 12 + development/example_jobs/test_job.py | 1 + development/nautobot_config.py | 3 - development/towncrier_template.j2 | 30 ++ docs/admin/uninstall.md | 6 + docs/assets/extra.css | 5 + docs/dev/contributing.md | 31 +- docs/dev/dev_environment.md | 4 +- nautobot_capacity_metrics/__init__.py | 4 +- nautobot_capacity_metrics/tests/test_basic.py | 12 - poetry.lock | 280 ++++++------------ pyproject.toml | 118 +++++++- tasks.py | 153 ++++++++-- 22 files changed, 465 insertions(+), 389 deletions(-) delete mode 100644 .github/workflows/rebake.yml create mode 100644 changes/.gitignore create mode 100644 changes/84.changed create mode 100644 development/towncrier_template.j2 diff --git a/.cookiecutter.json b/.cookiecutter.json index bdec77a..ef5a62a 100644 --- a/.cookiecutter.json +++ b/.cookiecutter.json @@ -21,7 +21,7 @@ "_drift_manager": { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "template_dir": "nautobot-app", - "template_ref": "develop", + "template_ref": "refs/tags/nautobot-app-v2.1.0", "cookie_dir": "", "branch_prefix": "drift-manager", "pull_request_strategy": "create", @@ -29,7 +29,7 @@ "black" ], "draft": true, - "baked_commit_ref": "e7aa0f1fcbb2737f76621c0e2a80bb6e32c0429d" + "baked_commit_ref": "678f632b2969fc9f5eb560968a1766f4ed6ddb46" } } } diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index b1eba07..bb2c361 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -1,5 +1,5 @@ " +issue_format = "[#{issue}](https://github.com/nautobot/nautobot-app-capacity-metrics/issues/{issue})" + +[[tool.towncrier.type]] +directory = "security" +name = "Security" +showcontent = true + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecated" +name = "Deprecated" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "dependencies" +name = "Dependencies" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true + +[[tool.towncrier.type]] +directory = "housekeeping" +name = "Housekeeping" +showcontent = true diff --git a/tasks.py b/tasks.py index 5bdd41c..5a5b459 100644 --- a/tasks.py +++ b/tasks.py @@ -13,6 +13,8 @@ """ import os +from pathlib import Path +from time import sleep from invoke.collection import Collection from invoke.tasks import task as invoke_task @@ -67,6 +69,25 @@ def _is_compose_included(context, name): return f"docker-compose.{name}.yml" in context.nautobot_capacity_metrics.compose_files +def _await_healthy_service(context, service): + container_id = docker_compose(context, f"ps -q -- {service}", pty=False, echo=False, hide=True).stdout.strip() + _await_healthy_container(context, container_id) + + +def _await_healthy_container(context, container_id): + while True: + result = context.run( + "docker inspect --format='{{.State.Health.Status}}' " + container_id, + pty=False, + echo=False, + hide=True, + ) + if result.stdout.strip() == "healthy": + break + print(f"Waiting for `{container_id}` container to become healthy ...") + sleep(1) + + def task(function=None, *args, **kwargs): """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace.""" @@ -216,11 +237,46 @@ def stop(context, service=""): docker_compose(context, "stop" if service else "down --remove-orphans", service=service) -@task -def destroy(context): +@task( + aliases=("down",), + help={ + "volumes": "Remove Docker compose volumes (default: True)", + "import-db-file": "Import database from `import-db-file` file into the fresh environment (default: empty)", + }, +) +def destroy(context, volumes=True, import_db_file=""): """Destroy all containers and volumes.""" print("Destroying Nautobot...") - docker_compose(context, "down --remove-orphans --volumes") + docker_compose(context, f"down --remove-orphans {'--volumes' if volumes else ''}") + + if not import_db_file: + return + + if not volumes: + raise ValueError("Cannot specify `--no-volumes` and `--import-db-file` arguments at the same time.") + + print(f"Importing database file: {import_db_file}...") + + input_path = Path(import_db_file).absolute() + if not input_path.is_file(): + raise ValueError(f"File not found: {input_path}") + + command = [ + "run", + "--rm", + "--detach", + f"--volume='{input_path}:/docker-entrypoint-initdb.d/dump.sql'", + "--", + "db", + ] + + container_id = docker_compose(context, " ".join(command), pty=False, echo=False, hide=True).stdout.strip() + _await_healthy_container(context, container_id) + print("Stopping database container...") + context.run(f"docker stop {container_id}", pty=False, echo=False, hide=True) + + print("Database import complete, you can start Nautobot with the following command:") + print("invoke start") @task @@ -424,27 +480,43 @@ def dbshell(context, db_name="", input_file="", output_file="", query=""): @task( help={ + "db-name": "Database name to create (default: Nautobot database)", "input-file": "SQL dump file to replace the existing database with. This can be generated using `invoke backup-db` (default: `dump.sql`).", } ) -def import_db(context, input_file="dump.sql"): - """Stop Nautobot containers and replace the current database with the dump into the running `db` container.""" - docker_compose(context, "stop -- nautobot worker") +def import_db(context, db_name="", input_file="dump.sql"): + """Stop Nautobot containers and replace the current database with the dump into `db` container.""" + docker_compose(context, "stop -- nautobot worker beat") + start(context, "db") + _await_healthy_service(context, "db") command = ["exec -- db sh -c '"] if _is_compose_included(context, "mysql"): + if not db_name: + db_name = "$MYSQL_DATABASE" command += [ + "mysql --user root --password=$MYSQL_ROOT_PASSWORD", + '--execute="', + f"DROP DATABASE IF EXISTS {db_name};", + f"CREATE DATABASE {db_name};", + "" + if db_name == "$MYSQL_DATABASE" + else f"GRANT ALL PRIVILEGES ON {db_name}.* TO $MYSQL_USER; FLUSH PRIVILEGES;", + '"', + "&&", "mysql", - "--database=$MYSQL_DATABASE", + f"--database={db_name}", "--user=$MYSQL_USER", "--password=$MYSQL_PASSWORD", ] elif _is_compose_included(context, "postgres"): + if not db_name: + db_name = "$POSTGRES_DB" command += [ - "psql", - "--username=$POSTGRES_USER", - "postgres", + f"dropdb --if-exists --user=$POSTGRES_USER {db_name} &&", + f"createdb --user=$POSTGRES_USER {db_name} &&", + f"psql --user=$POSTGRES_USER --dbname={db_name}", ] else: raise ValueError("Unsupported database backend.") @@ -467,7 +539,10 @@ def import_db(context, input_file="dump.sql"): } ) def backup_db(context, db_name="", output_file="dump.sql", readable=True): - """Dump database into `output_file` file from running `db` container.""" + """Dump database into `output_file` file from `db` container.""" + start(context, "db") + _await_healthy_service(context, "db") + command = ["exec -- db sh -c '"] if _is_compose_included(context, "mysql"): @@ -475,17 +550,12 @@ def backup_db(context, db_name="", output_file="dump.sql", readable=True): "mysqldump", "--user=root", "--password=$MYSQL_ROOT_PASSWORD", - "--add-drop-database", "--skip-extended-insert" if readable else "", - "--databases", db_name if db_name else "$MYSQL_DATABASE", ] elif _is_compose_included(context, "postgres"): command += [ "pg_dump", - "--clean", - "--create", - "--if-exists", "--username=$POSTGRES_USER", f"--dbname={db_name or '$POSTGRES_DB'}", "--inserts" if readable else "", @@ -542,6 +612,19 @@ def help_task(context): context.run(f"invoke {task_name} --help") +@task( + help={ + "version": "Version of Metrics & Monitoring Extension App to generate the release notes for.", + } +) +def generate_release_notes(context, version=""): + """Generate Release Notes using Towncrier.""" + command = "env DJANGO_SETTINGS_MODULE=nautobot.core.settings towncrier build" + if version: + command += f" --version {version}" + run_command(context, command) + + # ------------------------------------------------------------------------------ # TESTS # ------------------------------------------------------------------------------ @@ -583,12 +666,34 @@ def pylint(context): run_command(context, command) -@task -def pydocstyle(context): - """Run pydocstyle to validate docstring formatting adheres to NTC defined standards.""" - # We exclude the /migrations/ directory since it is autogenerated code - command = "pydocstyle ." - run_command(context, command) +@task(aliases=("a",)) +def autoformat(context): + """Run code autoformatting.""" + black(context, autoformat=True) + ruff(context, action="both", fix=True) + + +@task( + help={ + "action": "One of 'lint', 'format', or 'both'", + "fix": "Automatically fix selected action. May not be able to fix all.", + "output_format": "see https://docs.astral.sh/ruff/settings/#output-format", + }, +) +def ruff(context, action="lint", fix=False, output_format="text"): + """Run ruff to perform code formatting and/or linting.""" + if action != "lint": + command = "ruff format" + if not fix: + command += " --check" + command += " ." + run_command(context, command) + if action != "format": + command = "ruff check" + if fix: + command += " --fix" + command += f" --output-format {output_format} ." + run_command(context, command) @task @@ -677,12 +782,12 @@ def tests(context, failfast=False, keepdb=False, lint_only=False): # Sorted loosely from fastest to slowest print("Running black...") black(context) + print("Running ruff...") + ruff(context) print("Running flake8...") flake8(context) print("Running bandit...") bandit(context) - print("Running pydocstyle...") - pydocstyle(context) print("Running yamllint...") yamllint(context) print("Running poetry check...")