From f7544b23823a253aa5b0de62a088a3ab473091d7 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 12 Jul 2024 14:13:40 -0700 Subject: [PATCH 1/2] Remove initial Workflow Linter version 2 prior to moving to it's own repo --- lint-workflow-v2/.gitignore | 7 - lint-workflow-v2/.yamllint.yml | 36 - lint-workflow-v2/Pipfile | 24 - lint-workflow-v2/Pipfile.lock | 875 ------------------ lint-workflow-v2/README.md | 146 --- lint-workflow-v2/Taskfile.yml | 71 -- lint-workflow-v2/action.yml | 32 - lint-workflow-v2/actions.json.bak | 262 ------ lint-workflow-v2/pylintrc | 401 -------- lint-workflow-v2/pyproject.toml | 45 - lint-workflow-v2/pyproject.toml.tpl | 33 - lint-workflow-v2/settings.yaml | 8 - .../bitwarden_workflow_linter/__about__.py | 1 - .../src/bitwarden_workflow_linter/__init__.py | 0 .../src/bitwarden_workflow_linter/actions.py | 217 ----- .../src/bitwarden_workflow_linter/cli.py | 55 -- .../default_actions.json | 262 ------ .../default_settings.yaml | 8 - .../src/bitwarden_workflow_linter/lint.py | 173 ---- .../src/bitwarden_workflow_linter/load.py | 146 --- .../models/__init__.py | 0 .../bitwarden_workflow_linter/models/job.py | 56 -- .../bitwarden_workflow_linter/models/step.py | 48 - .../models/workflow.py | 45 - .../src/bitwarden_workflow_linter/rule.py | 101 -- .../rules/__init__.py | 0 .../rules/job_environment_prefix.py | 74 -- .../rules/name_capitalized.py | 56 -- .../rules/name_exists.py | 59 -- .../rules/pinned_job_runner.py | 54 -- .../rules/step_approved.py | 103 --- .../rules/step_pinned.py | 100 -- .../src/bitwarden_workflow_linter/utils.py | 180 ---- lint-workflow-v2/tests/__init__.py | 0 lint-workflow-v2/tests/conftest.py | 3 - lint-workflow-v2/tests/fixtures/test-alt.yml | 24 - .../tests/fixtures/test-min-incorrect.yaml | 9 - lint-workflow-v2/tests/fixtures/test-min.yaml | 13 - lint-workflow-v2/tests/fixtures/test.yml | 49 - lint-workflow-v2/tests/fixtures/test_a.yaml | 27 - lint-workflow-v2/tests/rules/__init__.py | 0 .../rules/test_job_environment_prefix.py | 110 --- .../tests/rules/test_name_capitalized.py | 107 --- .../tests/rules/test_name_exists.py | 75 -- .../tests/rules/test_pinned_job_runner.py | 65 -- .../tests/rules/test_step_approved.py | 113 --- .../tests/rules/test_step_pinned.py | 104 --- lint-workflow-v2/tests/test_job.py | 82 -- lint-workflow-v2/tests/test_lint.py | 47 - lint-workflow-v2/tests/test_load.py | 94 -- lint-workflow-v2/tests/test_rule.py | 140 --- lint-workflow-v2/tests/test_step.py | 78 -- lint-workflow-v2/tests/test_utils.py | 35 - lint-workflow-v2/tests/test_workflow.py | 100 -- 54 files changed, 4953 deletions(-) delete mode 100644 lint-workflow-v2/.gitignore delete mode 100644 lint-workflow-v2/.yamllint.yml delete mode 100644 lint-workflow-v2/Pipfile delete mode 100644 lint-workflow-v2/Pipfile.lock delete mode 100644 lint-workflow-v2/README.md delete mode 100644 lint-workflow-v2/Taskfile.yml delete mode 100644 lint-workflow-v2/action.yml delete mode 100644 lint-workflow-v2/actions.json.bak delete mode 100644 lint-workflow-v2/pylintrc delete mode 100644 lint-workflow-v2/pyproject.toml delete mode 100644 lint-workflow-v2/pyproject.toml.tpl delete mode 100644 lint-workflow-v2/settings.yaml delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/__about__.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/__init__.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/actions.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/cli.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/default_actions.json delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/default_settings.yaml delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/lint.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/load.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/models/__init__.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/models/job.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/models/step.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/models/workflow.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rule.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rules/__init__.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rules/job_environment_prefix.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_capitalized.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_exists.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rules/pinned_job_runner.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_approved.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_pinned.py delete mode 100644 lint-workflow-v2/src/bitwarden_workflow_linter/utils.py delete mode 100644 lint-workflow-v2/tests/__init__.py delete mode 100644 lint-workflow-v2/tests/conftest.py delete mode 100644 lint-workflow-v2/tests/fixtures/test-alt.yml delete mode 100644 lint-workflow-v2/tests/fixtures/test-min-incorrect.yaml delete mode 100644 lint-workflow-v2/tests/fixtures/test-min.yaml delete mode 100644 lint-workflow-v2/tests/fixtures/test.yml delete mode 100644 lint-workflow-v2/tests/fixtures/test_a.yaml delete mode 100644 lint-workflow-v2/tests/rules/__init__.py delete mode 100644 lint-workflow-v2/tests/rules/test_job_environment_prefix.py delete mode 100644 lint-workflow-v2/tests/rules/test_name_capitalized.py delete mode 100644 lint-workflow-v2/tests/rules/test_name_exists.py delete mode 100644 lint-workflow-v2/tests/rules/test_pinned_job_runner.py delete mode 100644 lint-workflow-v2/tests/rules/test_step_approved.py delete mode 100644 lint-workflow-v2/tests/rules/test_step_pinned.py delete mode 100644 lint-workflow-v2/tests/test_job.py delete mode 100644 lint-workflow-v2/tests/test_lint.py delete mode 100644 lint-workflow-v2/tests/test_load.py delete mode 100644 lint-workflow-v2/tests/test_rule.py delete mode 100644 lint-workflow-v2/tests/test_step.py delete mode 100644 lint-workflow-v2/tests/test_utils.py delete mode 100644 lint-workflow-v2/tests/test_workflow.py diff --git a/lint-workflow-v2/.gitignore b/lint-workflow-v2/.gitignore deleted file mode 100644 index 1e69ed6a..00000000 --- a/lint-workflow-v2/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.coverage -dist - -## Dev Environments -Session.vim -flake.nix -flake.lock diff --git a/lint-workflow-v2/.yamllint.yml b/lint-workflow-v2/.yamllint.yml deleted file mode 100644 index bed41fc5..00000000 --- a/lint-workflow-v2/.yamllint.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- - -extends: default - -rules: - braces: - level: warning - brackets: - level: warning - colons: - level: warning - commas: - level: warning - comments: - min-spaces-from-content: 1 - empty-lines: - level: warning - hyphens: - level: warning - indentation: - level: warning - spaces: 2 - key-duplicates: - level: warning - line-length: - level: warning - max: 120 - new-line-at-end-of-file: - level: warning - new-lines: - level: warning - trailing-spaces: - level: warning - truthy: - check-keys: false - level: warning diff --git a/lint-workflow-v2/Pipfile b/lint-workflow-v2/Pipfile deleted file mode 100644 index 8dc6f95b..00000000 --- a/lint-workflow-v2/Pipfile +++ /dev/null @@ -1,24 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -pyyaml = "*" -urllib3 = "*" -pydantic = "*" -"ruamel.yaml" = "*" -dataclasses-json = "*" - -[dev-packages] -black = "*" -pytest = "*" -coverage = "*" -pytest-cov = "*" -pylint = "*" -pytype = "*" -hatchling = "*" -build = "*" - -[requires] -python_version = "3.11" diff --git a/lint-workflow-v2/Pipfile.lock b/lint-workflow-v2/Pipfile.lock deleted file mode 100644 index a984d768..00000000 --- a/lint-workflow-v2/Pipfile.lock +++ /dev/null @@ -1,875 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "7e51933f1987a5e1d1182f4cec2a1c6f84194a17ac6653bb3674da0fe26dfb7f" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.11" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "annotated-types": { - "hashes": [ - "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", - "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" - ], - "markers": "python_version >= '3.8'", - "version": "==0.7.0" - }, - "dataclasses-json": { - "hashes": [ - "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", - "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0" - ], - "index": "pypi", - "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.6.7" - }, - "marshmallow": { - "hashes": [ - "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662", - "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1" - ], - "markers": "python_version >= '3.8'", - "version": "==3.21.3" - }, - "mypy-extensions": { - "hashes": [ - "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", - "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.0" - }, - "packaging": { - "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" - ], - "markers": "python_version >= '3.8'", - "version": "==24.1" - }, - "pydantic": { - "hashes": [ - "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52", - "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.7.4" - }, - "pydantic-core": { - "hashes": [ - "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3", - "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8", - "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8", - "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30", - "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a", - "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8", - "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d", - "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc", - "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2", - "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab", - "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077", - "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e", - "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9", - "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9", - "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef", - "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1", - "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507", - "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528", - "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558", - "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b", - "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154", - "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724", - "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695", - "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9", - "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851", - "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805", - "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a", - "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5", - "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94", - "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c", - "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d", - "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef", - "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26", - "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2", - "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c", - "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0", - "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2", - "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4", - "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d", - "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2", - "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce", - "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34", - "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f", - "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d", - "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b", - "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07", - "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312", - "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057", - "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d", - "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af", - "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb", - "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd", - "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78", - "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b", - "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223", - "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a", - "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4", - "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5", - "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23", - "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a", - "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4", - "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8", - "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d", - "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443", - "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e", - "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f", - "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e", - "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d", - "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc", - "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443", - "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be", - "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2", - "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee", - "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f", - "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae", - "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864", - "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4", - "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951", - "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc" - ], - "markers": "python_version >= '3.8'", - "version": "==2.18.4" - }, - "pyyaml": { - "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" - ], - "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==6.0.1" - }, - "ruamel.yaml": { - "hashes": [ - "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", - "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b" - ], - "markers": "python_version >= '3.7'", - "version": "==0.18.6" - }, - "ruamel.yaml.clib": { - "hashes": [ - "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", - "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", - "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", - "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", - "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", - "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b", - "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", - "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615", - "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", - "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", - "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", - "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1", - "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", - "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675", - "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", - "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7", - "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", - "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312", - "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa", - "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91", - "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b", - "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6", - "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3", - "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", - "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", - "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3", - "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe", - "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", - "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed", - "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337", - "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", - "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", - "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", - "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", - "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", - "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", - "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", - "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", - "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", - "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942", - "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", - "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", - "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", - "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", - "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28", - "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d", - "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", - "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", - "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875", - "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412" - ], - "markers": "platform_python_implementation == 'CPython' and python_version < '3.13'", - "version": "==0.2.8" - }, - "typing-extensions": { - "hashes": [ - "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", - "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" - ], - "markers": "python_version >= '3.8'", - "version": "==4.12.2" - }, - "typing-inspect": { - "hashes": [ - "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", - "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78" - ], - "version": "==0.9.0" - }, - "urllib3": { - "hashes": [ - "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", - "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.2.2" - } - }, - "develop": { - "astroid": { - "hashes": [ - "sha256:8ead48e31b92b2e217b6c9733a21afafe479d52d6e164dd25fb1a770c7c3cf94", - "sha256:e8a0083b4bb28fcffb6207a3bfc9e5d0a68be951dd7e336d5dcf639c682388c0" - ], - "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.2" - }, - "attrs": { - "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" - ], - "markers": "python_version >= '3.7'", - "version": "==23.2.0" - }, - "black": { - "hashes": [ - "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474", - "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1", - "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0", - "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8", - "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96", - "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1", - "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04", - "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021", - "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94", - "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d", - "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c", - "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7", - "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c", - "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc", - "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7", - "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d", - "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c", - "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741", - "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce", - "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb", - "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063", - "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==24.4.2" - }, - "build": { - "hashes": [ - "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d", - "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.2.1" - }, - "click": { - "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" - ], - "markers": "python_version >= '3.7'", - "version": "==8.1.7" - }, - "coverage": { - "extras": [ - "toml" - ], - "hashes": [ - "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f", - "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d", - "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747", - "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f", - "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d", - "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f", - "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47", - "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e", - "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba", - "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c", - "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b", - "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4", - "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7", - "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555", - "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233", - "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace", - "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805", - "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136", - "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4", - "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d", - "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806", - "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99", - "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8", - "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b", - "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5", - "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da", - "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0", - "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078", - "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f", - "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029", - "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353", - "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638", - "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9", - "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f", - "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7", - "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3", - "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e", - "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016", - "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088", - "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4", - "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882", - "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7", - "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53", - "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d", - "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080", - "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5", - "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d", - "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c", - "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8", - "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633", - "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9", - "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==7.5.4" - }, - "dill": { - "hashes": [ - "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", - "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7" - ], - "markers": "python_version >= '3.11'", - "version": "==0.3.8" - }, - "hatchling": { - "hashes": [ - "sha256:7064631a512610b52250a4d3ff1bd81551d6d1431c4eb7b72e734df6c74f4262", - "sha256:b47948e45d4d973034584dd4cb39c14b6a70227cf287ab7ec0ad7983408a882c" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.25.0" - }, - "immutabledict": { - "hashes": [ - "sha256:d728b2c2410d698d95e6200237feb50a695584d20289ad3379a439aa3d90baba", - "sha256:e003fd81aad2377a5a758bf7e1086cf3b70b63e9a5cc2f46bce8d0a2b4727c5f" - ], - "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==4.2.0" - }, - "importlab": { - "hashes": [ - "sha256:124cfa00e8a34fefe8aac1a5e94f56c781b178c9eb61a1d3f60f7e03b77338d3", - "sha256:b3893853b1f6eb027da509c3b40e6787e95dd66b4b66f1b3613aad77556e1465" - ], - "markers": "python_full_version >= '3.6.0'", - "version": "==0.8.1" - }, - "iniconfig": { - "hashes": [ - "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", - "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, - "isort": { - "hashes": [ - "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", - "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6" - ], - "markers": "python_full_version >= '3.8.0'", - "version": "==5.13.2" - }, - "jinja2": { - "hashes": [ - "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", - "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" - ], - "markers": "python_version >= '3.7'", - "version": "==3.1.4" - }, - "libcst": { - "hashes": [ - "sha256:061d6855ef30efe38b8a292b7e5d57c8e820e71fc9ec9846678b60a934b53bbb", - "sha256:17d71001cb25e94cfe8c3d997095741a8c4aa7a6d234c0f972bc42818c88dfaf", - "sha256:279b54568ea1f25add50ea4ba3d76d4f5835500c82f24d54daae4c5095b986aa", - "sha256:2d47de16d105e7dd5f4e01a428d9f4dc1e71efd74f79766daf54528ce37f23c3", - "sha256:3399e6c95df89921511b44d8c5bf6a75bcbc2d51f1f6429763609ba005c10f6b", - "sha256:3401dae41fe24565387a65baee3887e31a44e3e58066b0250bc3f3ccf85b1b5a", - "sha256:3c6a8faab9da48c5b371557d0999b4ca51f4f2cbd37ee8c2c4df0ac01c781465", - "sha256:449e0b16604f054fa7f27c3ffe86ea7ef6c409836fe68fe4e752a1894175db00", - "sha256:48601e3e590e2d6a7ab8c019cf3937c70511a78d778ab3333764531253acdb33", - "sha256:5da9d7dc83801aba3b8d911f82dc1a375db0d508318bad79d9fb245374afe068", - "sha256:62e2682ee1567b6a89c91853865372bf34f178bfd237853d84df2b87b446e654", - "sha256:7c54aa66c86d8ece9c93156a2cf5ca512b0dce40142fe9e072c86af2bf892411", - "sha256:7ece51d935bc9bf60b528473d2e5cc67cbb88e2f8146297e40ee2c7d80be6f13", - "sha256:81653dea1cdfa4c6520a7c5ffb95fa4d220cbd242e446c7a06d42d8636bfcbba", - "sha256:8e54c777b8d27339b70f304d16fc8bc8674ef1bd34ed05ea874bf4921eb5a313", - "sha256:9d0cc3c5a2a51fa7e1d579a828c0a2e46b2170024fd8b1a0691c8a52f3abb2d9", - "sha256:addc6d585141a7677591868886f6bda0577529401a59d210aa8112114340e129", - "sha256:b8ecdba8934632b4dadacb666cd3816627a6ead831b806336972ccc4ba7ca0e9", - "sha256:bb0abf627ee14903d05d0ad9b2c6865f1b21eb4081e2c7bea1033f85db2b8bae", - "sha256:cb4e42ea107a37bff7f9fdbee9532d39f9ea77b89caa5c5112b37057b12e0838", - "sha256:d024f44059a853b4b852cfc04fec33e346659d851371e46fc8e7c19de24d3da9", - "sha256:d1989fa12d3cd79118ebd29ebe2a6976d23d509b1a4226bc3d66fcb7cb50bd5d", - "sha256:e6227562fc5c9c1efd15dfe90b0971ae254461b8b6b23c1b617139b6003de1c1", - "sha256:f42797309bb725f0f000510d5463175ccd7155395f09b5e7723971b0007a976d", - "sha256:f6abce0e66bba2babfadc20530fd3688f672d565674336595b4623cd800b91ef" - ], - "markers": "python_version >= '3.9'", - "version": "==1.4.0" - }, - "markupsafe": { - "hashes": [ - "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", - "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", - "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", - "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", - "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", - "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", - "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", - "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", - "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", - "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", - "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", - "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", - "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", - "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", - "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", - "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", - "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", - "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", - "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", - "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", - "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", - "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", - "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", - "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", - "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", - "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", - "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", - "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", - "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", - "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", - "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", - "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", - "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", - "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", - "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", - "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", - "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", - "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", - "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", - "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", - "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", - "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", - "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", - "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", - "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", - "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", - "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", - "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", - "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", - "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", - "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", - "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", - "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", - "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", - "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", - "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", - "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", - "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", - "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", - "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.5" - }, - "mccabe": { - "hashes": [ - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, - "msgspec": { - "hashes": [ - "sha256:06acbd6edf175bee0e36295d6b0302c6de3aaf61246b46f9549ca0041a9d7177", - "sha256:0e24539b25c85c8f0597274f11061c102ad6b0c56af053373ba4629772b407be", - "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0", - "sha256:1a76b60e501b3932782a9da039bd1cd552b7d8dec54ce38332b87136c64852dd", - "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c", - "sha256:3ac4dd63fd5309dd42a8c8c36c1563531069152be7819518be0a9d03be9788e4", - "sha256:40a4df891676d9c28a67c2cc39947c33de516335680d1316a89e8f7218660410", - "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4", - "sha256:46eb2f6b22b0e61c137e65795b97dc515860bf6ec761d8fb65fdb62aa094ba61", - "sha256:6aa85198f8f154cf35d6f979998f6dadd3dc46a8a8c714632f53f5d65b315c07", - "sha256:7481355a1adcf1f08dedd9311193c674ffb8bf7b79314b4314752b89a2cf7f1c", - "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f", - "sha256:9080eb12b8f59e177bd1eb5c21e24dd2ba2fa88a1dbc9a98e05ad7779b54c681", - "sha256:974d3520fcc6b824a6dedbdf2b411df31a73e6e7414301abac62e6b8d03791b4", - "sha256:9da21f804c1a1471f26d32b5d9bc0480450ea77fbb8d9db431463ab64aaac2cf", - "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e", - "sha256:a6896f4cd5b4b7d688018805520769a8446df911eb93b421c6c68155cdf9dd5a", - "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca", - "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090", - "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b", - "sha256:c61ee4d3be03ea9cd089f7c8e36158786cd06e51fbb62529276452bbf2d52ece", - "sha256:c8355b55c80ac3e04885d72db515817d9fbb0def3bab936bba104e99ad22cf46", - "sha256:cc001cf39becf8d2dcd3f413a4797c55009b3a3cdbf78a8bf5a7ca8fdb76032c", - "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1", - "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492", - "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa", - "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6", - "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c", - "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57", - "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07", - "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f", - "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466", - "sha256:f7d9faed6dfff654a9ca7d9b0068456517f63dbc3aa704a527f493b9200b210a", - "sha256:fac5834e14ac4da1fca373753e0c4ec9c8069d1fe5f534fa5208453b6065d5be", - "sha256:fd62e5818731a66aaa8e9b0a1e5543dc979a46278da01e85c3c9a1a4f047ef7e", - "sha256:fda4c357145cf0b760000c4ad597e19b53adf01382b711f281720a10a0fe72b7" - ], - "markers": "python_version >= '3.8'", - "version": "==0.18.6" - }, - "mypy-extensions": { - "hashes": [ - "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", - "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.0" - }, - "networkx": { - "hashes": [ - "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36", - "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61" - ], - "markers": "python_version >= '3.8'", - "version": "==3.1" - }, - "ninja": { - "hashes": [ - "sha256:18302d96a5467ea98b68e1cae1ae4b4fb2b2a56a82b955193c637557c7273dbd", - "sha256:185e0641bde601e53841525c4196278e9aaf4463758da6dd1e752c0a0f54136a", - "sha256:376889c76d87b95b5719fdd61dd7db193aa7fd4432e5d52d2e44e4c497bdbbee", - "sha256:3e0f9be5bb20d74d58c66cc1c414c3e6aeb45c35b0d0e41e8d739c2c0d57784f", - "sha256:73b93c14046447c7c5cc892433d4fae65d6364bec6685411cb97a8bcf815f93a", - "sha256:7563ce1d9fe6ed5af0b8dd9ab4a214bf4ff1f2f6fd6dc29f480981f0f8b8b249", - "sha256:76482ba746a2618eecf89d5253c0d1e4f1da1270d41e9f54dfbd91831b0f6885", - "sha256:84502ec98f02a037a169c4b0d5d86075eaf6afc55e1879003d6cab51ced2ea4b", - "sha256:95da904130bfa02ea74ff9c0116b4ad266174fafb1c707aa50212bc7859aebf1", - "sha256:9d793b08dd857e38d0b6ffe9e6b7145d7c485a42dcfea04905ca0cdb6017cc3c", - "sha256:9df724344202b83018abb45cb1efc22efd337a1496514e7e6b3b59655be85205", - "sha256:aad34a70ef15b12519946c5633344bc775a7656d789d9ed5fdb0d456383716ef", - "sha256:d491fc8d89cdcb416107c349ad1e3a735d4c4af5e1cb8f5f727baca6350fdaea", - "sha256:ecf80cf5afd09f14dcceff28cb3f11dc90fb97c999c89307aea435889cb66877", - "sha256:fa2ba9d74acfdfbfbcf06fad1b8282de8a7a8c481d9dee45c859a8c93fcc1082" - ], - "version": "==1.11.1.1" - }, - "packaging": { - "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" - ], - "markers": "python_version >= '3.8'", - "version": "==24.1" - }, - "pathspec": { - "hashes": [ - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, - "platformdirs": { - "hashes": [ - "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", - "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" - ], - "markers": "python_version >= '3.8'", - "version": "==4.2.2" - }, - "pluggy": { - "hashes": [ - "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", - "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" - ], - "markers": "python_version >= '3.8'", - "version": "==1.5.0" - }, - "pycnite": { - "hashes": [ - "sha256:301c5d7e2e2f61e5535d0af7c4486f3ea4f5083c4808a7645a55ddb45cb25027", - "sha256:7346099b2313894db65ffca177f9e6eb3251fdfedb019eb6da02822c2ff8c1e1" - ], - "markers": "python_version >= '3.8'", - "version": "==2024.6.13" - }, - "pydot": { - "hashes": [ - "sha256:408a47913ea7bd5d2d34b274144880c1310c4aee901f353cf21fe2e526a4ea28", - "sha256:60246af215123fa062f21cd791be67dda23a6f280df09f68919e637a1e4f3235" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, - "pylint": { - "hashes": [ - "sha256:02f6c562b215582386068d52a30f520d84fdbcf2a95fc7e855b816060d048b60", - "sha256:b3d7d2708a3e04b4679e02d99e72329a8b7ee8afb8d04110682278781f889fa8" - ], - "index": "pypi", - "markers": "python_full_version >= '3.8.0'", - "version": "==3.2.3" - }, - "pyparsing": { - "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" - ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.1.2" - }, - "pyproject-hooks": { - "hashes": [ - "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965", - "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2" - ], - "markers": "python_version >= '3.7'", - "version": "==1.1.0" - }, - "pytest": { - "hashes": [ - "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343", - "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==8.2.2" - }, - "pytest-cov": { - "hashes": [ - "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", - "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==5.0.0" - }, - "pytype": { - "hashes": [ - "sha256:2986961d1a2ef778a9fea2f81c90cbe6f4a196ff783b26a30e7f3c9be1545c10", - "sha256:3315f51ce482d8c07e2f1857c47ccd17beb4a9bfde9afa7d1e6669f1e8881081", - "sha256:4484bbe24c58c289cea0c7549a507f20eea29caa68dcaf1e857eba584324641e", - "sha256:50da73a6a24337d00c9c88697ced74909dd00e3b9238ad619490ff83a6588d52", - "sha256:51c5470061ace9908a8306e3aa460e89e8fc15e63557625c387f932b852d3fa8", - "sha256:8ad2f7cbfc9843a3f6f1d776318a2f4d96b0ca2d054128280be58eef293c0795", - "sha256:a3483a44f457766be85c2a2ef779f3c23f2a95973e5fdb844a030149fc1d92e3", - "sha256:aa1069fcaaa9859273ec31650632e7bba9af0e82083e8f7d08f9b742d6d36055", - "sha256:ad59ed48b185d61f5ab8bceb173e6680eeabf109c5f15290ad88b7be1096e493", - "sha256:ae6c825b7067f420810f8846147df4fdc966c340a859f51317ba26c860cda906", - "sha256:b140b180cb287bbbce025e4c929cf3b020aecf4f91899ef609073ab02f2ba8e2", - "sha256:b3647b554eea009c9069d58440aed6b9e66d2f3ba20ef2674ffd5cd3b0bf83d7", - "sha256:fd24126c5b7bbda52fb48ad9d5b4811a7b090a944c8f39a45b94246668722e8c" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2024.4.11" - }, - "pyyaml": { - "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" - ], - "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==6.0.1" - }, - "tabulate": { - "hashes": [ - "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", - "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f" - ], - "markers": "python_version >= '3.7'", - "version": "==0.9.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==0.10.2" - }, - "tomlkit": { - "hashes": [ - "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f", - "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c" - ], - "markers": "python_version >= '3.7'", - "version": "==0.12.5" - }, - "trove-classifiers": { - "hashes": [ - "sha256:8a6242bbb5c9ae88d34cf665e816b287d2212973c8777dfaef5ec18d72ac1d03", - "sha256:c43ade18704823e4afa3d9db7083294bc4708a5e02afbcefacd0e9d03a7a24ef" - ], - "version": "==2024.5.22" - }, - "typing-extensions": { - "hashes": [ - "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", - "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" - ], - "markers": "python_version >= '3.8'", - "version": "==4.12.2" - } - } -} diff --git a/lint-workflow-v2/README.md b/lint-workflow-v2/README.md deleted file mode 100644 index 12dac3e1..00000000 --- a/lint-workflow-v2/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# Bitwarden Workflow Linter - -## Installation - -## PyPi -``` -Not yet implemented -``` - -### Locally -``` -git clone git@github.com:bitwarden/gh-actions.git -cd gh-actions/lint-workflow-v2 - -pip install -e . -``` - -## Usage -### Setup settings.yaml - -If a non-default configuration is desired (different than `src/bitwarden_workflow_linter/default_settings.yaml`), copy -the below and create a `settings.yaml` in the directory that `bwwl` will be running from. - -```yaml -enabled_rules: - - bitwarden_workflow_linter.rules.name_exists.RuleNameExists - - bitwarden_workflow_linter.rules.name_capitalized.RuleNameCapitalized - - bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned - - bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix - - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned - -approved_actions_path: default_actions.json -``` - - -``` -usage: bwwl [-h] [-v] {lint,actions} ... - -positional arguments: - {lint,actions} - lint Verify that a GitHub Action Workflow follows all of the Rules. - actions Add or Update Actions in the pre-approved list. - -options: - -h, --help show this help message and exit - -v, --verbose -``` - -## Development -### Requirements - -- Python 3.11 -- pipenv - -### Setup - -``` -pipenv install --dev -pipenv shell -``` - -### Testing - -All built-in `src/bitwarden_workflow_linter/rules` should have 100% code coverage and we should shoot for an overall coverage of 80%+. -We are lax on the -[imperative shell](https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell) -(code interacting with other systems; ie. disk, network, etc), but we strive to maintain a high coverage over the -functional core (objects and models). - -``` -pipenv shell -pytest tests --cov=src -``` - -### Code Reformatting - -We adhere to PEP8 and use `black` to maintain this adherence. `black` should be run on any change being merged -to `main`. - -``` -pipenv shell -black . -``` - -### Linting - -We loosely use [Google's Python style guide](https://google.github.io/styleguide/pyguide.html), but yield to -`black` when there is a conflict - -``` -pipenv shell -pylint --rcfile pylintrc src/ tests/ -``` - -### Add a new Rule - -A new Rule is created by extending the Rule base class and overriding the `fn(obj: Union[Workflow, Job, Step])` method. -Available attributes of `Workflows`, `Jobs` and `Steps` can be found in their definitons under `src/models`. - -For a simple example, we'll take a look at enforcing the existence of the `name` key in a Job. This is already done by -default with the src.rules.name_exists.RuleNameExists, but provides a simple enough example to walk through. - -```python -from typing import Union, Tuple - -from ..rule import Rule -from ..models.job import Job -from ..models.workflow import Workflow -from ..models.step import Step -from ..utils import LintLevels, Settings - - -class RuleJobNameExists(Rule): - def __init__(self, settings: Settings = None) -> None: - self.message = "name must exist" - self.on_fail: LintLevels = LintLevels.ERROR - self.compatibility: List[Union[Workflow, Job, Step]] = [Job] - self.settings: Settings = settings - - def fn(self, obj: Job) -> Tuple[bool, str]: - """ """ - if obj.name is not None: - return True, "" - return False, self.message -``` - -By default, a new Rule needs five things: - -- `self.message`: The message to return to the user on a lint failure -- `self.on_fail`: The level of failure on a lint failure (NONE, WARNING, ERROR). - NONE and WARNING will exit with a code of 0 (unless using `strict` mode for WARNING). - ERROR will exit with a non-zero exit code -- `self.compatibility`: The list of objects this rule is compatible with. This is used to create separate instances of - the Rule for each object in the Rules collection. -- `self.settings`: In general, this should default to what is shown here, but allows for overrides -- `self.fn`: The function doing the actual work to check the object and enforce the standard. - -`fn` can be as simple or as complex as it needs to be to run a check on a _single_ object. This linter currently does -not support Rules that check against multiple objects at a time OR file level formatting (one empty between each step or -two empty lines between each job) - - -### ToDo - -- [ ] Add Rule to assert correct format for single line run - diff --git a/lint-workflow-v2/Taskfile.yml b/lint-workflow-v2/Taskfile.yml deleted file mode 100644 index 00cc69c4..00000000 --- a/lint-workflow-v2/Taskfile.yml +++ /dev/null @@ -1,71 +0,0 @@ -# https://taskfile.dev - -version: '3' - -tasks: - fmt: - silent: true - cmds: - - pipenv run black . - - lint: - silent: true - cmds: - - pipenv run pylint --rcflie pylintrc {{.CLI_ARGS}} - - type: - silent: true - cmds: - - pipenv run pytype src - - update: - silent: true - cmds: - - | - deps=$(pipenv requirements --exclude-markers | tail -n +2 | awk '{print "\t\""$0"\","}') - export DEPS=$(printf "$deps") - envsubst < pyproject.toml.tpl > pyproject.toml - - install: - silent: true - cmds: - - task: update - - pipenv run python -m pip install -e . - - test:unit: - cmds: - - pipenv run pytest tests - - test:unit:single: - cmds: - - pipenv run pytest {{.CLI_ARGS}} - - test:cov: - cmds: - - pipenv run pytest --cov-report term --cov=src tests - - test:cov:detailed: - cmds: - - pipenv run pytest --cov-report term-missing --cov=src tests - - test:e2e:lint: - cmds: - - pipenv run bwwl lint --files tests/fixtures - - test:e2e:lint:single: - cmds: - - pipenv run bwwl lint --files tests/fixtures/test_a.yml - - test:e2e:actions:add: - cmds: - - pipenv run bwwl actions --output test.json add bitwarden/sm-action - - test:e2e:actions:update: - cmds: - - pipenv run bwwl actions --output test.json update - - dist: - silent: true - cmds: - - task: update - - pipenv run python -m build diff --git a/lint-workflow-v2/action.yml b/lint-workflow-v2/action.yml deleted file mode 100644 index 0ecde6ff..00000000 --- a/lint-workflow-v2/action.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: 'Lint Workflow' -description: 'Lints GitHub Actions Workflow' -inputs: - workflows: - description: "Path to workflow file(s)" - required: true -runs: - using: "composite" - steps: - - name: Install dependencies - run: pip install --user yamllint - shell: bash - - - name: Setup - id: setup - run: | - FORMAT_PATH=$(echo ${{ inputs.workflows }} | sed 's/ *$//') - echo "path=$FORMAT_PATH" >> $GITHUB_OUTPUT - shell: bash - - - name: Python lint - run: python ${{ github.action_path }}/lint.py "${{ steps.setup.outputs.path }}" - shell: bash - - - name: YAML lint - run: | - WORKFLOWS=($(echo "${{ steps.setup.outputs.path }}" | tr ' ' '\n')) - for WORKFLOW in "${WORKFLOWS[@]}"; do - yamllint -f colored -c ${{ github.action_path }}/.yamllint.yml $WORKFLOW - done - shell: bash - working-directory: ${{ github.workspace }} diff --git a/lint-workflow-v2/actions.json.bak b/lint-workflow-v2/actions.json.bak deleted file mode 100644 index f601cb89..00000000 --- a/lint-workflow-v2/actions.json.bak +++ /dev/null @@ -1,262 +0,0 @@ -{ - "Asana/create-app-attachment-github-action": { - "name": "Asana/create-app-attachment-github-action", - "sha": "affc72d57bac733d864d4189ed69a9cbd61a9e4f", - "version": "v1.3" - }, - "Azure/functions-action": { - "name": "Azure/functions-action", - "sha": "238dc3c45bb1b04e5d16ff9e75cddd1d86753bd6", - "version": "v1.5.1" - }, - "Azure/get-keyvault-secrets": { - "name": "Azure/get-keyvault-secrets", - "sha": "b5c723b9ac7870c022b8c35befe620b7009b336f", - "version": "v1" - }, - "Azure/login": { - "name": "Azure/login", - "sha": "de95379fe4dadc2defb305917eaa7e5dde727294", - "version": "v1.5.1" - }, - "Swatinem/rust-cache": { - "name": "Swatinem/rust-cache", - "sha": "a95ba195448af2da9b00fb742d14ffaaf3c21f43", - "version": "v2.7.0" - }, - "SwiftDocOrg/github-wiki-publish-action": { - "name": "SwiftDocOrg/github-wiki-publish-action", - "sha": "a87db85ed06e4431be29cfdcb22b9653881305d0", - "version": "1.0.0" - }, - "SwiftDocOrg/swift-doc": { - "name": "SwiftDocOrg/swift-doc", - "sha": "f935ebfe524a0ff27bda07dadc3662e3e45b5125", - "version": "1.0.0-rc.1" - }, - "act10ns/slack": { - "name": "act10ns/slack", - "sha": "ed1309ab9862e57e9e583e51c7889486b9a00b0f", - "version": "v2.0.0" - }, - "actions/cache": { - "name": "actions/cache", - "sha": "704facf57e6136b1bc63b828d79edcd491f0ee84", - "version": "v3.3.2" - }, - "actions/checkout": { - "name": "actions/checkout", - "sha": "b4ffde65f46336ab88eb53be808477a3936bae11", - "version": "v4.1.1" - }, - "actions/delete-package-versions": { - "name": "actions/delete-package-versions", - "sha": "0d39a63126868f5eefaa47169615edd3c0f61e20", - "version": "v4.1.1" - }, - "actions/download-artifact": { - "name": "actions/download-artifact", - "sha": "f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110", - "version": "v4.1.0" - }, - "actions/github-script": { - "name": "actions/github-script", - "sha": "60a0d83039c74a4aee543508d2ffcb1c3799cdea", - "version": "v7.0.1" - }, - "actions/labeler": { - "name": "actions/labeler", - "sha": "8558fd74291d67161a8a78ce36a881fa63b766a9", - "version": "v5.0.0" - }, - "actions/setup-dotnet": { - "name": "actions/setup-dotnet", - "sha": "4d6c8fcf3c8f7a60068d26b594648e99df24cee3", - "version": "v4.0.0" - }, - "actions/setup-java": { - "name": "actions/setup-java", - "sha": "387ac29b308b003ca37ba93a6cab5eb57c8f5f93", - "version": "v4.0.0" - }, - "actions/setup-node": { - "name": "actions/setup-node", - "sha": "b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8", - "version": "v4.0.1" - }, - "actions/setup-python": { - "name": "actions/setup-python", - "sha": "0a5c61591373683505ea898e09a3ea4f39ef2b9c", - "version": "v5.0.0" - }, - "actions/stale": { - "name": "actions/stale", - "sha": "28ca1036281a5e5922ead5184a1bbf96e5fc984e", - "version": "v9.0.0" - }, - "actions/upload-artifact": { - "name": "actions/upload-artifact", - "sha": "c7d193f32edcb7bfad88892161225aeda64e9392", - "version": "v4.0.0" - }, - "android-actions/setup-android": { - "name": "android-actions/setup-android", - "sha": "07976c6290703d34c16d382cb36445f98bb43b1f", - "version": "v3.2.0" - }, - "azure/webapps-deploy": { - "name": "azure/webapps-deploy", - "sha": "145a0687697df1d8a28909569f6e5d86213041f9", - "version": "v3.0.0" - }, - "bitwarden/sm-action": { - "name": "bitwarden/sm-action", - "sha": "92d1d6a4f26a89a8191c83ab531a53544578f182", - "version": "v2.0.0" - }, - "checkmarx/ast-github-action": { - "name": "checkmarx/ast-github-action", - "sha": "72d549beebd0bc5bbafa559f198161b6ce7c03df", - "version": "2.0.21" - }, - "chrnorm/deployment-action": { - "name": "chrnorm/deployment-action", - "sha": "d42cde7132fcec920de534fffc3be83794335c00", - "version": "v2.0.5" - }, - "chrnorm/deployment-status": { - "name": "chrnorm/deployment-status", - "sha": "2afb7d27101260f4a764219439564d954d10b5b0", - "version": "v2.0.1" - }, - "chromaui/action": { - "name": "chromaui/action", - "sha": "80bf5911f28005ed208f15b7268843b79ca0e23a", - "version": "v1" - }, - "cloudflare/pages-action": { - "name": "cloudflare/pages-action", - "sha": "f0a1cd58cd66095dee69bfa18fa5efd1dde93bca", - "version": "v1.5.0" - }, - "convictional/trigger-workflow-and-wait": { - "name": "convictional/trigger-workflow-and-wait", - "sha": "f69fa9eedd3c62a599220f4d5745230e237904be", - "version": "v1.6.5" - }, - "crazy-max/ghaction-import-gpg": { - "name": "crazy-max/ghaction-import-gpg", - "sha": "01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4", - "version": "v6.1.0" - }, - "crowdin/github-action": { - "name": "crowdin/github-action", - "sha": "fdc55cdc519e86e32c22a07528d649277f1127f2", - "version": "v1.16.0" - }, - "dawidd6/action-download-artifact": { - "name": "dawidd6/action-download-artifact", - "sha": "e7466d1a7587ed14867642c2ca74b5bcc1e19a2d", - "version": "v3.0.0" - }, - "dawidd6/action-homebrew-bump-formula": { - "name": "dawidd6/action-homebrew-bump-formula", - "sha": "75ed025ff3ad1d617862838b342b06d613a0ddf3", - "version": "v3.10.1" - }, - "digitalocean/action-doctl": { - "name": "digitalocean/action-doctl", - "sha": "e5cb5b0cde9789f79c5115c2c4d902f38a708804", - "version": "v2.5.0" - }, - "docker/build-push-action": { - "name": "docker/build-push-action", - "sha": "4a13e500e55cf31b7a5d59a38ab2040ab0f42f56", - "version": "v5.1.0" - }, - "docker/setup-buildx-action": { - "name": "docker/setup-buildx-action", - "sha": "f95db51fddba0c2d1ec667646a06c2ce06100226", - "version": "v3.0.0" - }, - "docker/setup-qemu-action": { - "name": "docker/setup-qemu-action", - "sha": "68827325e0b33c7199eb31dd4e31fbe9023e06e3", - "version": "v3.0.0" - }, - "dorny/test-reporter": { - "name": "dorny/test-reporter", - "sha": "afe6793191b75b608954023a46831a3fe10048d4", - "version": "v1.7.0" - }, - "dtolnay/rust-toolchain": { - "name": "dtolnay/rust-toolchain", - "sha": "1482605bfc5719782e1267fd0c0cc350fe7646b8", - "version": "v1" - }, - "futureware-tech/simulator-action": { - "name": "futureware-tech/simulator-action", - "sha": "bfa03d93ec9de6dacb0c5553bbf8da8afc6c2ee9", - "version": "v3" - }, - "hashicorp/setup-packer": { - "name": "hashicorp/setup-packer", - "sha": "ecc5516821087666a672c0d280a0084ea6d9aafd", - "version": "v2.0.1" - }, - "macauley/action-homebrew-bump-cask": { - "name": "macauley/action-homebrew-bump-cask", - "sha": "445c42390d790569d938f9068d01af39ca030feb", - "version": "v1.0.0" - }, - "microsoft/setup-msbuild": { - "name": "microsoft/setup-msbuild", - "sha": "1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c", - "version": "v1.3.1" - }, - "ncipollo/release-action": { - "name": "ncipollo/release-action", - "sha": "6c75be85e571768fa31b40abf38de58ba0397db5", - "version": "v1.13.0" - }, - "peter-evans/close-issue": { - "name": "peter-evans/close-issue", - "sha": "276d7966e389d888f011539a86c8920025ea0626", - "version": "v3.0.1" - }, - "ruby/setup-ruby": { - "name": "ruby/setup-ruby", - "sha": "360dc864d5da99d54fcb8e9148c14a84b90d3e88", - "version": "v1.165.1" - }, - "samuelmeuli/action-snapcraft": { - "name": "samuelmeuli/action-snapcraft", - "sha": "d33c176a9b784876d966f80fb1b461808edc0641", - "version": "v2.1.1" - }, - "snapcore/action-build": { - "name": "snapcore/action-build", - "sha": "2096990827aa966f773676c8a53793c723b6b40f", - "version": "v1.2.0" - }, - "sonarsource/sonarcloud-github-action": { - "name": "sonarsource/sonarcloud-github-action", - "sha": "49e6cd3b187936a73b8280d59ffd9da69df63ec9", - "version": "v2.1.1" - }, - "stackrox/kube-linter-action": { - "name": "stackrox/kube-linter-action", - "sha": "ca0d55b925470deb5b04b556e6c4276ea94d03c3", - "version": "v1.0.4" - }, - "tj-actions/changed-files": { - "name": "tj-actions/changed-files", - "sha": "716b1e13042866565e00e85fd4ec490e186c4a2f", - "version": "v41.0.1" - }, - "yogevbd/enforce-label-action": { - "name": "yogevbd/enforce-label-action", - "sha": "a3c219da6b8fa73f6ba62b68ff09c469b3a1c024", - "version": "2.2.2" - } -} diff --git a/lint-workflow-v2/pylintrc b/lint-workflow-v2/pylintrc deleted file mode 100644 index e2378102..00000000 --- a/lint-workflow-v2/pylintrc +++ /dev/null @@ -1,401 +0,0 @@ -# This Pylint rcfile contains a best-effort configuration to uphold the -# best-practices and style described in the Google Python style guide: -# https://google.github.io/styleguide/pyguide.html -# -# Its canonical open-source location is: -# https://google.github.io/styleguide/pylintrc - -[MAIN] - -# Files or directories to be skipped. They should be base names, not paths. -ignore=third_party - -# Files or directories matching the regex patterns are skipped. The regex -# matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# 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= - -# 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 reenable 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=R, - abstract-method, - apply-builtin, - arguments-differ, - attribute-defined-outside-init, - backtick, - bad-option-value, - basestring-builtin, - buffer-builtin, - c-extension-no-member, - consider-using-enumerate, - cmp-builtin, - cmp-method, - coerce-builtin, - coerce-method, - delslice-method, - div-method, - eq-without-hash, - execfile-builtin, - file-builtin, - filter-builtin-not-iterating, - fixme, - getslice-method, - global-statement, - hex-method, - idiv-method, - implicit-str-concat, - import-error, - import-self, - import-star-module-level, - input-builtin, - intern-builtin, - invalid-str-codec, - locally-disabled, - long-builtin, - long-suffix, - map-builtin-not-iterating, - misplaced-comparison-constant, - missing-function-docstring, - metaclass-assignment, - next-method-called, - next-method-defined, - no-absolute-import, - no-init, # added - no-member, - no-name-in-module, - no-self-use, - nonzero-method, - oct-method, - old-division, - old-ne-operator, - old-octal-literal, - old-raise-syntax, - parameter-unpacking, - print-statement, - raising-string, - range-builtin-not-iterating, - raw_input-builtin, - rdiv-method, - reduce-builtin, - relative-import, - reload-builtin, - round-builtin, - setslice-method, - signature-differs, - standarderror-builtin, - suppressed-message, - sys-max-int, - trailing-newlines, - unichr-builtin, - unicode-builtin, - unnecessary-pass, - unpacking-in-except, - useless-else-on-loop, - useless-suppression, - using-cmp-argument, - wrong-import-order, - xrange-builtin, - zip-builtin-not-iterating, - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=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= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=main,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl - -# Regular expression matching correct function names -function-rgx=^(?:(?PsetUp|tearDown|setUpModule|tearDownModule)|(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ - -# Regular expression matching correct variable names -variable-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct constant names -const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct attribute names -attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ - -# Regular expression matching correct argument names -argument-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=^[a-z][a-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=^_?[A-Z][a-zA-Z0-9]*$ - -# Regular expression matching correct module names -module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$ - -# Regular expression matching correct method names -method-rgx=(?x)^(?:(?P_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P_{0,2}[a-z][a-z0-9_]*))$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=12 - - -[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,contextlib2.contextmanager - -# List of module names for which member attributes should not be checked -# (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= - -# 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 - -# 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= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=88 - -# TODO(https://github.com/pylint-dev/pylint/issues/3352): Direct pylint to exempt -# lines made too long by directives to pytype. - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=(?x)( - ^\s*(\#\ )??$| - ^\s*(from\s+\S+\s+)?import\s+.+$) - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=yes - -# Maximum number of lines in a module -max-module-lines=99999 - -# String used as indentation unit. The internal Google style guide mandates 2 -# spaces. Google's externaly-published style guide says 4, consistent with -# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google -# projects (like TensorFlow). - -# Overriden to 4 to conform with black -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=TODO - - -[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=yes - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-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 - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging,absl.logging,tensorflow.io.logging - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub, - TERMIOS, - Bastion, - rexec, - sets - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in 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, absl - -# 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 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls, - class_ - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs diff --git a/lint-workflow-v2/pyproject.toml b/lint-workflow-v2/pyproject.toml deleted file mode 100644 index cf65d04f..00000000 --- a/lint-workflow-v2/pyproject.toml +++ /dev/null @@ -1,45 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "bitwarden_workflow_linter" -dynamic = ["version"] -authors = ["Bitwarden Inc"] -description = "Custom GitHub Action Workflow Linter" -readme = "README.md" -requires-python = ">=3.11" -classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Operating System :: OS Independent", -] - -dependencies = [ - "annotated-types==0.6.0", - "dataclasses-json==0.6.4", - "marshmallow==3.20.2", - "mypy-extensions==1.0.0", - "packaging==23.2", - "pydantic==2.6.0", - "pydantic-core==2.16.1", - "pyyaml==6.0.1", - "ruamel.yaml==0.18.5", - "ruamel.yaml.clib==0.2.8", - "typing-extensions==4.9.0", - "typing-inspect==0.9.0", - "urllib3==2.2.0", -] - -[project.urls] -Homepage = "https://github.com/bitwarden/gh-actions/tree/main/lint-workflow-v2" -Issues = "https://github.com/bitwarden/gh-actions/issues" - -[project.scripts] -bwwl = "bitwarden_workflow_linter.cli:main" - -[tool.hatch.build.targets.wheel] -packages = ["src/bitwarden_workflow_linter"] - -[tool.hatch.version] -path = "src/bitwarden_workflow_linter/__about__.py" diff --git a/lint-workflow-v2/pyproject.toml.tpl b/lint-workflow-v2/pyproject.toml.tpl deleted file mode 100644 index 0f12f7d9..00000000 --- a/lint-workflow-v2/pyproject.toml.tpl +++ /dev/null @@ -1,33 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "bitwarden_workflow_linter" -dynamic = ["version"] -authors = [] -description = "Custom GitHub Action Workflow Linter" -readme = "README.md" -requires-python = ">=3.11" -classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Operating System :: OS Independent", -] - -dependencies = [ -$DEPS -] - -[project.urls] -Homepage = "https://github.com/bitwarden/gh-actions/tree/main/lint-workflow-v2" -Issues = "https://github.com/bitwarden/gh-actions/issues" - -[project.scripts] -bwwl = "bitwarden_workflow_linter.cli:main" - -[tool.hatch.build.targets.wheel] -packages = ["src/bitwarden_workflow_linter"] - -[tool.hatch.version] -path = "src/bitwarden_workflow_linter/__about__.py" diff --git a/lint-workflow-v2/settings.yaml b/lint-workflow-v2/settings.yaml deleted file mode 100644 index 8b21d5cc..00000000 --- a/lint-workflow-v2/settings.yaml +++ /dev/null @@ -1,8 +0,0 @@ -enabled_rules: - - bitwarden_workflow_linter.rules.name_exists.RuleNameExists - - bitwarden_workflow_linter.rules.name_capitalized.RuleNameCapitalized - - bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned - - bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix - - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned - -approved_actions_path: default_actions.json diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/__about__.py b/lint-workflow-v2/src/bitwarden_workflow_linter/__about__.py deleted file mode 100644 index cf04467c..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/__about__.py +++ /dev/null @@ -1 +0,0 @@ -version = "0.0.3" diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/__init__.py b/lint-workflow-v2/src/bitwarden_workflow_linter/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/actions.py b/lint-workflow-v2/src/bitwarden_workflow_linter/actions.py deleted file mode 100644 index 88698127..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/actions.py +++ /dev/null @@ -1,217 +0,0 @@ -"""Module providing Actions subcommand to manage list of pre-approved Actions.""" - -import argparse -import json -import logging -import os -import urllib3 as urllib - -from dataclasses import asdict -from typing import Optional, Tuple, Union - -from .utils import Colors, Settings, Action - - -class GitHubApiSchemaError(Exception): - """A generic Exception to catch redefinitions of GitHub Api Schema changes.""" - - pass - - -class ActionsCmd: - """Command to manage the pre-approved list of Actions - - This class contains logic to manage the list of pre-approved actions - to include: - - updating the action data in the list - - adding a new pre-approved action to the list with the data from the - latest release - - This class also includes supporting logic to interact with GitHub - - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """Initialize the ActionsCmd class. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.settings = settings - - @staticmethod - def extend_parser( - subparsers: argparse._SubParsersAction, - ) -> argparse._SubParsersAction: - """Extends the CLI subparser with the options for ActionCmd. - - Add 'actions add' and 'actions update' to the CLI as subcommands - along with the options and arguments for each. - - Args: - subparsers: - The main argument parser to add subcommands and arguments to - """ - parser_actions = subparsers.add_parser( - "actions", help="!!BETA!!\nAdd or Update Actions in the pre-approved list." - ) - parser_actions.add_argument( - "-o", "--output", action="store", default="actions.json" - ) - subparsers_actions = parser_actions.add_subparsers( - required=True, dest="actions_command" - ) - subparsers_actions.add_parser("update", help="update action versions") - parser_actions_add = subparsers_actions.add_parser( - "add", help="add action to approved list" - ) - parser_actions_add.add_argument("name", help="action name [git owner/repo]") - - return subparsers - - def get_github_api_response( - self, url: str, action_name: str - ) -> Union[urllib.response.BaseHTTPResponse, None]: - """Call GitHub API with error logging without throwing an exception.""" - - http = urllib.PoolManager() - headers = {"user-agent": "bw-linter"} - - if os.getenv("GITHUB_TOKEN", None): - headers["Authorization"] = f"Token {os.environ['GITHUB_TOKEN']}" - - response = http.request("GET", url, headers=headers) - - if response.status == 403 and response.reason == "rate limit exceeded": - logging.error( - "Failed to call GitHub API for action: %s due to rate limit exceeded.", - action_name, - ) - return None - - if response.status == 401 and response.reason == "Unauthorized": - logging.error( - "Failed to call GitHub API for action: %s: %s.", - action_name, - response.data, - ) - return None - - return response - - def exists(self, action: Action) -> bool: - """Takes an action id and checks if the action repository exists.""" - - url = f"https://api.github.com/repos/{action.name}" - response = self.get_github_api_response(url, action.name) - - if response is None: - # Handle exceeding GitHub API limit by returning that the action exists - # without actually checking to prevent false errors on linter output. Only - # show it as an linter error. - return True - - if response.status == 404: - return False - - return True - - def get_latest_version(self, action: Action) -> Action | None: - """Gets the latest version of the Action to compare against.""" - - try: - # Get tag from latest release - response = self.get_github_api_response( - f"https://api.github.com/repos/{action.name}/releases/latest", - action.name, - ) - if not response: - return None - - tag_name = json.loads(response.data)["tag_name"] - - # Get the URL to the commit for the tag - response = self.get_github_api_response( - f"https://api.github.com/repos/{action.name}/git/ref/tags/{tag_name}", - action.name, - ) - if not response: - return None - - if json.loads(response.data)["object"]["type"] == "commit": - sha = json.loads(response.data)["object"]["sha"] - else: - url = json.loads(response.data)["object"]["url"] - # Follow the URL and get the commit sha for tags - response = self.get_github_api_response(url, action.name) - if not response: - return None - - sha = json.loads(response.data)["object"]["sha"] - except KeyError as err: - raise GitHubApiSchemaError( - f"Error with the GitHub API Response Schema for either /releases or /tags: {err}" - ) - - return Action(name=action.name, version=tag_name, sha=sha) - - def save_actions(self, updated_actions: dict[str, Action], filename: str) -> None: - """Save Actions to disk. - - This is used to track the list of approved actions. - """ - with open(filename, "w", encoding="utf8") as action_file: - converted_updated_actions = { - name: asdict(action) for name, action in updated_actions.items() - } - action_file.write( - json.dumps(converted_updated_actions, indent=2, sort_keys=True) - ) - - def add(self, new_action_name: str, filename: str) -> int: - """Subcommand to add a new Action to the list of approved Actions. - - 'actions add' will add an Action and all of its metadata and dump all - approved actions (including the new one) to either the default JSON file - or the one provided by '--output' - """ - print("Actions: add") - updated_actions = self.settings.approved_actions - proposed_action = Action(name=new_action_name) - - if self.exists(proposed_action): - latest = self.get_latest_version(proposed_action) - if latest: - updated_actions[latest.name] = latest - - self.save_actions(updated_actions, filename) - return 0 - - def update(self, filename: str) -> int: - """Subcommand to update all of the versions of the approved actions. - - 'actions update' will update all of the approved actions to the newest - version and dump all of the new data to either the default JSON file or - the one provided by '--output' - """ - print("Actions: update") - updated_actions = {} - for action in self.settings.approved_actions.values(): - if self.exists(action): - latest_release = self.get_latest_version(action) - if action != latest_release: - print( - ( - f" - {action.name} \033[{Colors.yellow}changed\033[0m: " - f"({action.version}, {action.sha}) => (" - f"{latest_release.version}, {latest_release.sha})" - ) - ) - else: - print(f" - {action.name} \033[{Colors.green}ok\033[0m") - updated_actions[action.name] = latest_release - - self.save_actions(updated_actions, filename) - return 0 diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/cli.py b/lint-workflow-v2/src/bitwarden_workflow_linter/cli.py deleted file mode 100644 index 6f9e662a..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/cli.py +++ /dev/null @@ -1,55 +0,0 @@ -"""This is the entrypoint module for the workflow-linter CLI.""" - -import argparse -import sys - -from typing import List, Optional - -from .actions import ActionsCmd -from .lint import LinterCmd -from .utils import Settings - - -local_settings = Settings.factory() - - -def main(input_args: Optional[List[str]] = None) -> int: - """CLI utility to lint GitHub Action Workflows. - - A CLI utility to enforce coding standards on GitHub Action workflows. The - utility also provides other subcommands to assist with other workflow - maintenance tasks; such as maintaining the list of approved GitHub Actions. - """ - linter_cmd = LinterCmd(settings=local_settings) - actions_cmd = ActionsCmd(settings=local_settings) - - # Read arguments from command line. - parser = argparse.ArgumentParser(prog="bwwl") - parser.add_argument("-v", "--verbose", action="store_true", default=False) - subparsers = parser.add_subparsers(required=True, dest="command") - - subparsers = LinterCmd.extend_parser(subparsers) - subparsers = ActionsCmd.extend_parser(subparsers) - - # Pull the arguments from the command line - input_args = sys.argv[1:] - if not input_args: - raise SystemExit(parser.print_help()) - - args = parser.parse_args(input_args) - - if args.command == "lint": - return linter_cmd.run(args.files, args.strict) - - if args.command == "actions": - print(f"{'-'*50}\n!!bwwl actions is in BETA!!\n{'-'*50}") - if args.actions_command == "add": - return actions_cmd.add(args.name, args.output) - if args.actions_command == "update": - return actions_cmd.update(args.output) - - return -1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/default_actions.json b/lint-workflow-v2/src/bitwarden_workflow_linter/default_actions.json deleted file mode 100644 index f601cb89..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/default_actions.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "Asana/create-app-attachment-github-action": { - "name": "Asana/create-app-attachment-github-action", - "sha": "affc72d57bac733d864d4189ed69a9cbd61a9e4f", - "version": "v1.3" - }, - "Azure/functions-action": { - "name": "Azure/functions-action", - "sha": "238dc3c45bb1b04e5d16ff9e75cddd1d86753bd6", - "version": "v1.5.1" - }, - "Azure/get-keyvault-secrets": { - "name": "Azure/get-keyvault-secrets", - "sha": "b5c723b9ac7870c022b8c35befe620b7009b336f", - "version": "v1" - }, - "Azure/login": { - "name": "Azure/login", - "sha": "de95379fe4dadc2defb305917eaa7e5dde727294", - "version": "v1.5.1" - }, - "Swatinem/rust-cache": { - "name": "Swatinem/rust-cache", - "sha": "a95ba195448af2da9b00fb742d14ffaaf3c21f43", - "version": "v2.7.0" - }, - "SwiftDocOrg/github-wiki-publish-action": { - "name": "SwiftDocOrg/github-wiki-publish-action", - "sha": "a87db85ed06e4431be29cfdcb22b9653881305d0", - "version": "1.0.0" - }, - "SwiftDocOrg/swift-doc": { - "name": "SwiftDocOrg/swift-doc", - "sha": "f935ebfe524a0ff27bda07dadc3662e3e45b5125", - "version": "1.0.0-rc.1" - }, - "act10ns/slack": { - "name": "act10ns/slack", - "sha": "ed1309ab9862e57e9e583e51c7889486b9a00b0f", - "version": "v2.0.0" - }, - "actions/cache": { - "name": "actions/cache", - "sha": "704facf57e6136b1bc63b828d79edcd491f0ee84", - "version": "v3.3.2" - }, - "actions/checkout": { - "name": "actions/checkout", - "sha": "b4ffde65f46336ab88eb53be808477a3936bae11", - "version": "v4.1.1" - }, - "actions/delete-package-versions": { - "name": "actions/delete-package-versions", - "sha": "0d39a63126868f5eefaa47169615edd3c0f61e20", - "version": "v4.1.1" - }, - "actions/download-artifact": { - "name": "actions/download-artifact", - "sha": "f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110", - "version": "v4.1.0" - }, - "actions/github-script": { - "name": "actions/github-script", - "sha": "60a0d83039c74a4aee543508d2ffcb1c3799cdea", - "version": "v7.0.1" - }, - "actions/labeler": { - "name": "actions/labeler", - "sha": "8558fd74291d67161a8a78ce36a881fa63b766a9", - "version": "v5.0.0" - }, - "actions/setup-dotnet": { - "name": "actions/setup-dotnet", - "sha": "4d6c8fcf3c8f7a60068d26b594648e99df24cee3", - "version": "v4.0.0" - }, - "actions/setup-java": { - "name": "actions/setup-java", - "sha": "387ac29b308b003ca37ba93a6cab5eb57c8f5f93", - "version": "v4.0.0" - }, - "actions/setup-node": { - "name": "actions/setup-node", - "sha": "b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8", - "version": "v4.0.1" - }, - "actions/setup-python": { - "name": "actions/setup-python", - "sha": "0a5c61591373683505ea898e09a3ea4f39ef2b9c", - "version": "v5.0.0" - }, - "actions/stale": { - "name": "actions/stale", - "sha": "28ca1036281a5e5922ead5184a1bbf96e5fc984e", - "version": "v9.0.0" - }, - "actions/upload-artifact": { - "name": "actions/upload-artifact", - "sha": "c7d193f32edcb7bfad88892161225aeda64e9392", - "version": "v4.0.0" - }, - "android-actions/setup-android": { - "name": "android-actions/setup-android", - "sha": "07976c6290703d34c16d382cb36445f98bb43b1f", - "version": "v3.2.0" - }, - "azure/webapps-deploy": { - "name": "azure/webapps-deploy", - "sha": "145a0687697df1d8a28909569f6e5d86213041f9", - "version": "v3.0.0" - }, - "bitwarden/sm-action": { - "name": "bitwarden/sm-action", - "sha": "92d1d6a4f26a89a8191c83ab531a53544578f182", - "version": "v2.0.0" - }, - "checkmarx/ast-github-action": { - "name": "checkmarx/ast-github-action", - "sha": "72d549beebd0bc5bbafa559f198161b6ce7c03df", - "version": "2.0.21" - }, - "chrnorm/deployment-action": { - "name": "chrnorm/deployment-action", - "sha": "d42cde7132fcec920de534fffc3be83794335c00", - "version": "v2.0.5" - }, - "chrnorm/deployment-status": { - "name": "chrnorm/deployment-status", - "sha": "2afb7d27101260f4a764219439564d954d10b5b0", - "version": "v2.0.1" - }, - "chromaui/action": { - "name": "chromaui/action", - "sha": "80bf5911f28005ed208f15b7268843b79ca0e23a", - "version": "v1" - }, - "cloudflare/pages-action": { - "name": "cloudflare/pages-action", - "sha": "f0a1cd58cd66095dee69bfa18fa5efd1dde93bca", - "version": "v1.5.0" - }, - "convictional/trigger-workflow-and-wait": { - "name": "convictional/trigger-workflow-and-wait", - "sha": "f69fa9eedd3c62a599220f4d5745230e237904be", - "version": "v1.6.5" - }, - "crazy-max/ghaction-import-gpg": { - "name": "crazy-max/ghaction-import-gpg", - "sha": "01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4", - "version": "v6.1.0" - }, - "crowdin/github-action": { - "name": "crowdin/github-action", - "sha": "fdc55cdc519e86e32c22a07528d649277f1127f2", - "version": "v1.16.0" - }, - "dawidd6/action-download-artifact": { - "name": "dawidd6/action-download-artifact", - "sha": "e7466d1a7587ed14867642c2ca74b5bcc1e19a2d", - "version": "v3.0.0" - }, - "dawidd6/action-homebrew-bump-formula": { - "name": "dawidd6/action-homebrew-bump-formula", - "sha": "75ed025ff3ad1d617862838b342b06d613a0ddf3", - "version": "v3.10.1" - }, - "digitalocean/action-doctl": { - "name": "digitalocean/action-doctl", - "sha": "e5cb5b0cde9789f79c5115c2c4d902f38a708804", - "version": "v2.5.0" - }, - "docker/build-push-action": { - "name": "docker/build-push-action", - "sha": "4a13e500e55cf31b7a5d59a38ab2040ab0f42f56", - "version": "v5.1.0" - }, - "docker/setup-buildx-action": { - "name": "docker/setup-buildx-action", - "sha": "f95db51fddba0c2d1ec667646a06c2ce06100226", - "version": "v3.0.0" - }, - "docker/setup-qemu-action": { - "name": "docker/setup-qemu-action", - "sha": "68827325e0b33c7199eb31dd4e31fbe9023e06e3", - "version": "v3.0.0" - }, - "dorny/test-reporter": { - "name": "dorny/test-reporter", - "sha": "afe6793191b75b608954023a46831a3fe10048d4", - "version": "v1.7.0" - }, - "dtolnay/rust-toolchain": { - "name": "dtolnay/rust-toolchain", - "sha": "1482605bfc5719782e1267fd0c0cc350fe7646b8", - "version": "v1" - }, - "futureware-tech/simulator-action": { - "name": "futureware-tech/simulator-action", - "sha": "bfa03d93ec9de6dacb0c5553bbf8da8afc6c2ee9", - "version": "v3" - }, - "hashicorp/setup-packer": { - "name": "hashicorp/setup-packer", - "sha": "ecc5516821087666a672c0d280a0084ea6d9aafd", - "version": "v2.0.1" - }, - "macauley/action-homebrew-bump-cask": { - "name": "macauley/action-homebrew-bump-cask", - "sha": "445c42390d790569d938f9068d01af39ca030feb", - "version": "v1.0.0" - }, - "microsoft/setup-msbuild": { - "name": "microsoft/setup-msbuild", - "sha": "1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c", - "version": "v1.3.1" - }, - "ncipollo/release-action": { - "name": "ncipollo/release-action", - "sha": "6c75be85e571768fa31b40abf38de58ba0397db5", - "version": "v1.13.0" - }, - "peter-evans/close-issue": { - "name": "peter-evans/close-issue", - "sha": "276d7966e389d888f011539a86c8920025ea0626", - "version": "v3.0.1" - }, - "ruby/setup-ruby": { - "name": "ruby/setup-ruby", - "sha": "360dc864d5da99d54fcb8e9148c14a84b90d3e88", - "version": "v1.165.1" - }, - "samuelmeuli/action-snapcraft": { - "name": "samuelmeuli/action-snapcraft", - "sha": "d33c176a9b784876d966f80fb1b461808edc0641", - "version": "v2.1.1" - }, - "snapcore/action-build": { - "name": "snapcore/action-build", - "sha": "2096990827aa966f773676c8a53793c723b6b40f", - "version": "v1.2.0" - }, - "sonarsource/sonarcloud-github-action": { - "name": "sonarsource/sonarcloud-github-action", - "sha": "49e6cd3b187936a73b8280d59ffd9da69df63ec9", - "version": "v2.1.1" - }, - "stackrox/kube-linter-action": { - "name": "stackrox/kube-linter-action", - "sha": "ca0d55b925470deb5b04b556e6c4276ea94d03c3", - "version": "v1.0.4" - }, - "tj-actions/changed-files": { - "name": "tj-actions/changed-files", - "sha": "716b1e13042866565e00e85fd4ec490e186c4a2f", - "version": "v41.0.1" - }, - "yogevbd/enforce-label-action": { - "name": "yogevbd/enforce-label-action", - "sha": "a3c219da6b8fa73f6ba62b68ff09c469b3a1c024", - "version": "2.2.2" - } -} diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/default_settings.yaml b/lint-workflow-v2/src/bitwarden_workflow_linter/default_settings.yaml deleted file mode 100644 index 8b21d5cc..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/default_settings.yaml +++ /dev/null @@ -1,8 +0,0 @@ -enabled_rules: - - bitwarden_workflow_linter.rules.name_exists.RuleNameExists - - bitwarden_workflow_linter.rules.name_capitalized.RuleNameCapitalized - - bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned - - bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix - - bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned - -approved_actions_path: default_actions.json diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/lint.py b/lint-workflow-v2/src/bitwarden_workflow_linter/lint.py deleted file mode 100644 index ebc18165..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/lint.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Module providing Lint subcommand to run custom linting rules against GitHub Action -Workflows.""" - -import argparse -import os - -from functools import reduce -from typing import Optional - -from .load import WorkflowBuilder, Rules -from .utils import LintFinding, Settings - - -class LinterCmd: - """Command to lint GitHub Action Workflow files - - This class contains logic to lint workflows that are passed in. - Supporting logic is supplied to: - - build out the list of Rules desired - - select and validate the workflow files to lint - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """Initailize the LinterCmd class. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.rules = Rules(settings=settings) - - @staticmethod - def extend_parser( - subparsers: argparse._SubParsersAction, - ) -> argparse._SubParsersAction: - """Extends the CLI subparser with the options for LintCmd. - - Add 'lint' as a subcommand along with its options and arguments - - Args: - subparsers: - The main argument parser to add subcommands and arguments to - """ - parser_lint = subparsers.add_parser( - "lint", - help="Verify that a GitHub Action Workflow follows all of the Rules.", - ) - parser_lint.add_argument( - "-s", - "--strict", - action="store_true", - help="return non-zero exit code on warnings as well as errors", - ) - parser_lint.add_argument("-f", "--files", action="append", help="files to lint") - parser_lint.add_argument( - "--output", - action="store", - help="output format: [stdout|json|md]", - default="stdout", - ) - return subparsers - - def get_max_error_level(self, findings: list[LintFinding]) -> int: - """Get max error level from list of findings. - - Compute the maximum error level to determine the exit code required. - # if max(error) return exit(1); else return exit(0) - - Args: - findings: - All of the findings that the linter found while linting a workflow. - - Return: - The numeric value of the maximum lint finding - """ - if len(findings) == 0: - return 0 - return max(findings, key=lambda finding: finding.level.code).level.code - - def lint_file(self, filename: str) -> int: - """Lint a single workflow. - - Run all of the Workflow, Job, and Step level rules that have been enabled. - - Args: - filename: - The name of the file that contains the workflow to lint - - Returns: - The maximum error level found in the file (none, warning, error) to - calculate the exit code from. - """ - findings = [] - max_error_level = 0 - - print(f"Linting: {filename}") - workflow = WorkflowBuilder.build(filename) - - for rule in self.rules.workflow: - findings.append(rule.execute(workflow)) - - for _, job in workflow.jobs.items(): - for rule in self.rules.job: - findings.append(rule.execute(job)) - - if job.steps is not None: - for step in job.steps: - for rule in self.rules.step: - findings.append(rule.execute(step)) - - findings = list(filter(lambda a: a is not None, findings)) - - if len(findings) > 0: - for finding in findings: - print(f" - {finding}") - print() - - max_error_level = self.get_max_error_level(findings) - - return max_error_level - - def generate_files(self, files: list[str]) -> list[str]: - """Generate the list of files to lint. - - Searches the list of directory and/or files taken from the CLI. - - Args: - files: - list of file names or directory names. - - Returns: - A sorted set of all workflow files in the path(s) specified. - """ - workflow_files = [] - for path in files: - if os.path.isfile(path): - workflow_files.append(path) - elif os.path.isdir(path): - for subdir, _, files in os.walk(path): - for filename in files: - filepath = subdir + os.sep + filename - if filepath.endswith((".yml", ".yaml")): - workflow_files.append(filepath) - - return sorted(set(workflow_files)) - - def run(self, input_files: list[str], strict: bool = False) -> int: - """Execute the LinterCmd. - - Args: - input_files: - list of file names or directory names. - strict: - fail on WARNING instead of succeed - - Returns - The return_code for the entire CLI to indicate success/failure - """ - files = self.generate_files(input_files) - - if len(input_files) > 0: - return_code = reduce( - lambda a, b: a if a > b else b, map(self.lint_file, files) - ) - - if return_code == 1 and not strict: - return_code = 0 - - return return_code - else: - print(f'File(s)/Directory: "{input_files}" does not exist, exiting.') - return -1 diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/load.py b/lint-workflow-v2/src/bitwarden_workflow_linter/load.py deleted file mode 100644 index 7f863725..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/load.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Module to load for Workflows and Rules.""" - -import importlib - -from typing import List, Optional - -from ruamel.yaml import YAML -from ruamel.yaml.comments import CommentedMap - -from .models.job import Job -from .models.step import Step -from .models.workflow import Workflow -from .rule import Rule -from .utils import Settings - - -yaml = YAML() - - -class WorkflowBuilderError(Exception): - """Exception to indicate an error with the WorkflowBuilder.""" - - pass - - -class WorkflowBuilder: - """Collection of methods to build Workflow objects.""" - - @classmethod - def __load_workflow_from_file(cls, filename: str) -> CommentedMap: - """Load YAML from disk. - - Args: - filename: - The name of the YAML file to read. - - Returns: - A CommentedMap that contains the dict() representation of the - YAML file. It includes the comments as a part of their respective - objects (depending on their location in the file). - """ - with open(filename, encoding="utf8") as file: - return yaml.load(file) - - @classmethod - def __build_workflow(cls, loaded_yaml: CommentedMap) -> Workflow: - """Parse the YAML and build out the workflow to run Rules against. - - Args: - loaded_yaml: - YAML that was loaded from either code or a file - - Returns - A Workflow to run linting Rules against - """ - return Workflow.init("", loaded_yaml) - - @classmethod - def build( - cls, - filename: Optional[str] = None, - workflow: Optional[CommentedMap] = None, - from_file: bool = True, - ) -> Workflow: - """Build a Workflow from either code or a file. - - This is a method that assists in testing by abstracting the disk IO - and allows for passing in a YAML object in code. - - Args: - filename: - The name of the file to load the YAML workflow from - yaml: - Pre-loaded YAML of a workflow - from_file: - Flag to determine if the YAML has already been loaded or needs to - be loaded from disk - """ - if from_file and filename is not None: - return cls.__build_workflow(cls.__load_workflow_from_file(filename)) - elif not from_file and workflow is not None: - return cls.__build_workflow(workflow) - - raise WorkflowBuilderError( - "The workflow must either be built from a file or from a CommentedMap" - ) - - -class LoadRulesError(Exception): - """Exception to indicate an error with loading rules.""" - - pass - - -class Rules: - """A collection of all of the types of rules. - - Rules is used as a collection of which Rules apply to which parts of the - workflow. It also assists in making sure the Rules that apply to multiple - types are not skipped. - """ - - workflow: List[Rule] = [] - job: List[Rule] = [] - step: List[Rule] = [] - - def __init__(self, settings: Settings) -> None: - """Initializes the Rules - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - # [TODO]: data resiliency - for rule in settings.enabled_rules: - module_name = rule.split(".") - module_name = ".".join(module_name[:-1]) - rule_name = rule.split(".")[-1] - - try: - rule_class = getattr(importlib.import_module(module_name), rule_name) - rule_inst = rule_class(settings=settings) - - if Workflow in rule_inst.compatibility: - self.workflow.append(rule_inst) - if Job in rule_inst.compatibility: - self.job.append(rule_inst) - if Step in rule_inst.compatibility: - self.step.append(rule_inst) - except LoadRulesError as err: - print(f"Error loading: {rule}\n{err}") - - def list(self) -> None: - """Print the loaded Rules.""" - print("===== Loaded Rules =====") - print("workflow rules:") - for rule in self.workflow: - print(f" - {type(rule).__name__}") - print("job rules:") - for rule in self.job: - print(f" - {type(rule).__name__}") - print("step rules:") - for rule in self.step: - print(f" - {type(rule).__name__}") - print("========================\n") diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/models/__init__.py b/lint-workflow-v2/src/bitwarden_workflow_linter/models/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/models/job.py b/lint-workflow-v2/src/bitwarden_workflow_linter/models/job.py deleted file mode 100644 index 8915ba33..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/models/job.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Representation for a job in a GitHub Action workflow.""" - -from dataclasses import dataclass, field -from typing import List, Optional, Self - -from dataclasses_json import config, dataclass_json, Undefined -from ruamel.yaml.comments import CommentedMap - -from .step import Step - - -@dataclass_json(undefined=Undefined.EXCLUDE) -@dataclass -class Job: - """Represents a job in a GitHub Action workflow. - - This object contains all of the data that is required to run the current linting - Rules against. If a new Rule requires a key that is missing, the attribute should - be added to this class to make it available for use in linting. - """ - - runs_on: Optional[str] = field(metadata=config(field_name="runs-on"), default=None) - key: Optional[str] = None - name: Optional[str] = None - env: Optional[CommentedMap] = None - steps: Optional[List[Step]] = None - uses: Optional[str] = None - uses_path: Optional[str] = None - uses_ref: Optional[str] = None - uses_with: Optional[CommentedMap] = field( - metadata=config(field_name="with"), default=None - ) - - @classmethod - def init(cls: Self, key: str, data: CommentedMap) -> Self: - """Custom dataclass constructor to map job data to a Job.""" - init_data = { - "key": key, - "name": data["name"] if "name" in data else None, - "runs-on": data["runs-on"] if "runs-on" in data else None, - "env": data["env"] if "env" in data else None, - } - - new_job = cls.from_dict(init_data) - - if "steps" in data: - new_job.steps = [ - Step.init(idx, new_job.key, step_data) - for idx, step_data in enumerate(data["steps"]) - ] - else: - new_job.uses = data["uses"].replace("\n", "") - if "@" in new_job.uses: - new_job.uses_path, new_job.uses_ref = new_job.uses.split("@") - - return new_job diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/models/step.py b/lint-workflow-v2/src/bitwarden_workflow_linter/models/step.py deleted file mode 100644 index e18f89d3..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/models/step.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Representation for a job step in a GitHub Action workflow.""" - -from dataclasses import dataclass, field -from typing import Optional, Self - -from dataclasses_json import config, dataclass_json, Undefined -from ruamel.yaml.comments import CommentedMap - - -@dataclass_json(undefined=Undefined.EXCLUDE) -@dataclass -class Step: - """Represents a step in a GitHub Action workflow job. - - This object contains all of the data that is required to run the current linting - Rules against. If a new Rule requires a key that is missing, the attribute should - be added to this class to make it available for use in linting. - """ - - key: Optional[int] = None - job: Optional[str] = None - name: Optional[str] = None - env: Optional[CommentedMap] = None - uses: Optional[str] = None - uses_path: Optional[str] = None - uses_ref: Optional[str] = None - uses_comment: Optional[str] = None - uses_version: Optional[str] = None - uses_with: Optional[CommentedMap] = field( - metadata=config(field_name="with"), default=None - ) - run: Optional[str] = None - - @classmethod - def init(cls: Self, idx: int, job: str, data: CommentedMap) -> Self: - """Custom dataclass constructor to map a job step data to a Step.""" - new_step = cls.from_dict(data) - - new_step.key = idx - new_step.job = job - - if "uses" in data.ca.items and data.ca.items["uses"][2]: - new_step.uses_comment = data.ca.items["uses"][2].value.replace("\n", "") - if "@" in new_step.uses: - new_step.uses_path, new_step.uses_ref = new_step.uses.split("@") - new_step.uses_version = new_step.uses_comment.split(" ")[-1] - - return new_step diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/models/workflow.py b/lint-workflow-v2/src/bitwarden_workflow_linter/models/workflow.py deleted file mode 100644 index 9c909695..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/models/workflow.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Representation for an entire GitHub Action workflow.""" - -from dataclasses import dataclass -from typing import Dict, Optional, Self - -from dataclasses_json import dataclass_json, Undefined -from ruamel.yaml.comments import CommentedMap - -from .job import Job - - -@dataclass_json(undefined=Undefined.EXCLUDE) -@dataclass -class Workflow: - """Represents an entire workflow in a GitHub Action workflow. - - This object contains all of the data that is required to run the current linting - Rules against. If a new Rule requires a key that is missing, the attribute should - be added to this class to make it available for use in linting. - - See src/models/job.py for an example if the key in the workflow data does not map - one-to-one in the model (ex. 'with' => 'uses_with') - """ - - key: str = "" - name: Optional[str] = None - on: Optional[CommentedMap] = None - jobs: Optional[Dict[str, Job]] = None - - @classmethod - def init(cls: Self, key: str, data: CommentedMap) -> Self: - init_data = { - "key": key, - "name": data["name"] if "name" in data else None, - "on": data["on"] if "on" in data else None, - } - - new_workflow = cls.from_dict(init_data) - - new_workflow.jobs = { - str(job_key): Job.init(job_key, job) - for job_key, job in data["jobs"].items() - } - - return new_workflow diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rule.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rule.py deleted file mode 100644 index c48a8b6d..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/rule.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Base Rule class to build rules by extending.""" - -from typing import List, Optional, Tuple, Union - -from .models.workflow import Workflow -from .models.job import Job -from .models.step import Step -from .utils import LintFinding, LintLevels, Settings - - -class RuleExecutionException(Exception): - """Exception for the Base Rule class.""" - - pass - - -class Rule: - """Base class of a Rule to extend to create a linting Rule.""" - - on_fail: LintLevels = LintLevels.ERROR - compatibility: List[Union[Workflow, Job, Step]] = [Workflow, Job, Step] - settings: Optional[Settings] = None - - def fn(self, obj: Union[Workflow, Job, Step]) -> Tuple[bool, str]: - """Execute the Rule (this should be overridden in the extending class. - - Args: - obj: - The object that the Rule is to be run against - - Returns: - The success/failure of the result of the Rule ran on the input. - """ - return False, f"{obj.name}: " - - def build_lint_message(self, message: str, obj: Union[Workflow, Job, Step]) -> str: - """Build the lint failure message. - - Build the lint failure message depending on the type of object that the - Rule is being run against. - - Args: - message: - The message body of the failure - obj: - The object the Rule is being run against - - Returns: - The type specific failure message - """ - obj_type = type(obj) - - if obj_type == Step: - return f"{obj_type.__name__} [{obj.job}.{obj.key}] => {message}" - elif obj_type == Job: - return f"{obj_type.__name__} [{obj.key}] => {message}" - else: - return f"{obj_type.__name__} => {message}" - - def execute(self, obj: Union[Workflow, Job, Step]) -> Union[LintFinding, None]: - """Wrapper function to execute the overridden self.fn(). - - Run the Rule against the object and return the results. The result - could be an Exception message where the Rule cannot be run against - the object for whatever reason. If an exception doesn't occur, the - result is linting success or failure. - - Args: - obj: - The object the Rule is being run against - - Returns: - A LintFinding object that contains the message to print to the user - and a LintLevel that contains the level of error to calculate the - exit code with. - """ - message = None - - if type(obj) not in self.compatibility: - return LintFinding( - self.build_lint_message( - f"{type(obj).__name__} not compatible with {type(self).__name__}", - obj, - ), - LintLevels.ERROR, - ) - - try: - passed, message = self.fn(obj) - - if passed: - return None - except RuleExecutionException as err: - return LintFinding( - self.build_lint_message( - f"failed to apply {type(self).__name__}\n{err}", obj - ), - LintLevels.ERROR, - ) - - return LintFinding(self.build_lint_message(message, obj), self.on_fail) diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/__init__.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rules/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/job_environment_prefix.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rules/job_environment_prefix.py deleted file mode 100644 index ec031752..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/job_environment_prefix.py +++ /dev/null @@ -1,74 +0,0 @@ -"""A Rule to enforce prefixes environment variables.""" - -from typing import Union, Optional, Tuple, List - -from ..models.job import Job -from ..models.step import Step -from ..models.workflow import Workflow -from ..rule import Rule -from ..utils import LintLevels, Settings - - -class RuleJobEnvironmentPrefix(Rule): - """Rule to enforce specific prefixes for environment variables. - - Automated testing is not easily written for GitHub Action Workflows. CI can also - get complicated really quickly and take up hundreds of lines. All of this can - make it very difficult to debug and troubleshoot, especially when environment - variables can be set in four different places: Workflow level, Job level, Step - level, and inside a shell Step. - - To alleviate some of the pain, we have decided that all Job level environment - variables should be prefixed with an underscore. All Workflow environment - variables are normally at the top of the file and Step level ones are pretty - visible when debugging a shell Step. - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """RuleJobEnvironmentPrefix constructor to override the Rule class. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.message = "Job environment vars should start with an underscore:" - self.on_fail = LintLevels.ERROR - self.compatibility = [Job] - self.settings = settings - - def fn(self, obj: Job) -> Tuple[bool, str]: - """Enforces the underscore prefix standard on job envs. - - Example: - --- - on: - workflow_dispatch: - - jobs: - job-key: - runs-on: ubuntu-22.04 - env: - _TEST_ENV: "test" - steps: - - run: echo test - - All keys under jobs.job-key.env should be prefixed with an underscore - as in _TEST_ENV. - - See tests/rules/test_job_environment_prefix.py for examples of - incorrectly named environment variables. - """ - correct = True - - if obj.env: - offending_keys = [] - for key in obj.env.keys(): - if key[0] != "_": - offending_keys.append(key) - correct = False - - if correct: - return True, "" - - return False, f"{self.message} ({' ,'.join(offending_keys)})" diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_capitalized.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_capitalized.py deleted file mode 100644 index d27dc98c..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_capitalized.py +++ /dev/null @@ -1,56 +0,0 @@ -"""A Rule to enforce all 'name' values start with a capital letter.""" - -from typing import Optional, Tuple, Union - -from ..models.job import Job -from ..models.step import Step -from ..models.workflow import Workflow -from ..rule import Rule -from ..utils import LintLevels, Settings - - -class RuleNameCapitalized(Rule): - """Rule to enforce all 'name' values start with a capital letter. - - A simple standard to help keep uniformity in naming. - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """Constructor for RuleNameCapitalized to override the Rule class. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.message = "name must capitalized" - self.on_fail = LintLevels.ERROR - self.settings = settings - - def fn(self, obj: Union[Workflow, Job, Step]) -> Tuple[bool, str]: - """Enforces capitalization of the first letter of any name key. - - Example: - --- - name: Test Workflow - - on: - workflow_dispatch: - - jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test - - 'Test Workflow', 'Test', and 'Test' all start with a capital letter. - - See tests/rules/test_name_capitalized.py for examples of incorrectly - capitalized names. This Rule DOES NOT enforce that the name exists. - It only enforces capitalization IF it does. - """ - if obj.name: - return obj.name[0].isupper(), self.message - return True, "" # Force passing if obj.name doesn't exist diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_exists.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_exists.py deleted file mode 100644 index 94fc9237..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/name_exists.py +++ /dev/null @@ -1,59 +0,0 @@ -"""A Rule to enforce that a 'name' key exists.""" - -from typing import Optional, Tuple, Union - -from ..models.workflow import Workflow -from ..models.job import Job -from ..models.step import Step -from ..rule import Rule -from ..utils import LintLevels, Settings - - -class RuleNameExists(Rule): - """Rule to enforce a 'name' key exists for every object in GitHub Actions. - - For pipeline run troubleshooting and debugging, it is helpful to have a - name to immediately identify a Workflow, Job, or Step while moving between - run and the code. - - It also helps with uniformity of runs. - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """Constructor for RuleNameCapitalized to override Rule class. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.message = "name must exist" - self.on_fail = LintLevels.ERROR - self.settings = settings - - def fn(self, obj: Union[Workflow, Job, Step]) -> Tuple[bool, str]: - """Enforces the existence of names. - - Example: - --- - name: Test Workflow - - on: - workflow_dispatch: - - jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test - - 'Test Workflow', 'Test', and 'Test' all exist. - - See tests/rules/test_name_exists.py for examples where a name does not - exist. - """ - if obj.name is not None: - return True, "" - return False, self.message diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/pinned_job_runner.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rules/pinned_job_runner.py deleted file mode 100644 index b84d2c92..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/pinned_job_runner.py +++ /dev/null @@ -1,54 +0,0 @@ -"""A Rule to enforce pinning runners to a specific OS version.""" - -from typing import List, Optional, Tuple, Union - -from ..models.job import Job -from ..models.step import Step -from ..models.workflow import Workflow -from ..rule import Rule -from ..utils import LintLevels, Settings - - -class RuleJobRunnerVersionPinned(Rule): - """Rule to enforce pinned Runner OS versions. - - Using `*-latest` versions will update automatically and has broken all of - our workflows in the past. To avoid this and prevent a single event from - breaking the majority of our pipelines, we pin the versions. - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """Constructor for RuleJobRunnerVersionPinned to override Rule class. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.message = "Workflow runner must be pinned" - self.on_fail = LintLevels.ERROR - self.compatibility = [Job] - self.settings = settings - - def fn(self, obj: Job) -> Tuple[bool, str]: - """Enforces runners are pinned to a version - - Example: - --- - on: - workflow_dispatch: - - jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - run: echo test - - call-workflow: - uses: bitwarden/server/.github/workflows/workflow-linter.yml@master - - 'runs-on' is pinned to '22.04' instead of 'latest' - """ - if obj.runs_on is not None and "latest" in obj.runs_on: - return False, self.message - return True, "" diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_approved.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_approved.py deleted file mode 100644 index ac6d831a..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_approved.py +++ /dev/null @@ -1,103 +0,0 @@ -"""A Rule to enforce the use of a list of pre-approved Actions.""" - -from typing import List, Optional, Tuple, Union - -from ..models.job import Job -from ..models.step import Step -from ..models.workflow import Workflow -from ..rule import Rule -from ..utils import LintLevels, Settings - - -class RuleStepUsesApproved(Rule): - """Rule to enforce that all Actions have been pre-approved. - - To limit the surface area of a supply chain attack in our pipelines, all Actions - are required to pass a security review and be added to the pre-approved list to - check against. - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """Constructor for RuleStepUsesApproved to override Rule class. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.on_fail = LintLevels.WARNING - self.compatibility = [Step] - self.settings = settings - - def skip(self, obj: Step) -> bool: - """Skip this Rule on some Steps. - - This Rule does not apply to a few types of Steps. These - Rules are skipped. - """ - ## Force pass for any shell steps - if not obj.uses: - return True - - ## Force pass for any local actions - if "@" not in obj.uses: - return True - - ## Force pass for any bitwarden/gh-actions - if obj.uses.startswith("bitwarden/gh-actions"): - return True - - return False - - def fn(self, obj: Step) -> Tuple[bool, str]: - """Enforces all externally used Actions are on the pre-approved list. - - The pre-approved list allows tight auditing on what Actions are trusted - and allowed to be run in our environments. This helps mitigate risks - against supply chain attacks in our pipelines. - - Example: - --- - on: - workflow_dispatch: - - jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - name: Checkout Branch - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Test Bitwarden Action - uses: bitwarden/gh-actions/get-keyvault-secrets@main - - - name: Test Local Action - uses: ./actions/test-action - - - name: Test Run Action - run: echo "test" - - In this example, 'actions/checkout' must be on the pre-approved list - and the metadata must match in order to succeed. The other three - Steps will be skipped. - """ - if self.skip(obj): - return True, "" - - # Actions in bitwarden/gh-actions are auto-approved - if obj.uses and not obj.uses_path in self.settings.approved_actions: - return False, ( - f"New Action detected: {obj.uses_path}\nFor security purposes, " - "actions must be reviewed and be on the pre-approved list" - ) - - action = self.settings.approved_actions[obj.uses_path] - - if obj.uses_version != action.version or obj.uses_ref != action.sha: - return False, ( - "Action is out of date. Please update to:\n" - f" commit: {action.version}" - f" version: {action.sha}" - ) - - return True, "" diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_pinned.py b/lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_pinned.py deleted file mode 100644 index 295a3f70..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/rules/step_pinned.py +++ /dev/null @@ -1,100 +0,0 @@ -"""A Rule to enforce Actions are pinned correctly.""" - -from typing import List, Optional, Tuple, Union - -from ..models.job import Job -from ..models.workflow import Workflow -from ..models.step import Step -from ..rule import Rule -from ..utils import LintLevels, Settings - - -class RuleStepUsesPinned(Rule): - """Rule to contain the enforcement logic for pinning Actions versions. - - Definition of Internal Action: - An Action that exists in the `bitwarden/gh-actions` GitHub Repository. - - For any external Action (any Action that does not fit the above definition of - an Internal Action), to mitigate the risks of supply chain attacks in our CI - pipelines, we pin any use of an Action to a specific hash that has been verified - and pre-approved after a security audit of the version of the Action. - - All Internal Actions, should be pinned to 'main'. This prevents Renovate from - spamming a bunch of PRs across all of our repos when `bitwarden/gh-actions` is - updated. - """ - - def __init__(self, settings: Optional[Settings] = None) -> None: - """Constructor for RuleStepUsesPinned to override base Rule. - - Args: - settings: - A Settings object that contains any default, overridden, or custom settings - required anywhere in the application. - """ - self.on_fail = LintLevels.ERROR - self.compatibility = [Step] - self.settings = settings - - def skip(self, obj: Step) -> bool: - """Skip this Rule on some Steps. - - This Rule does not apply to a few types of Steps. These - Rules are skipped. - """ - if not obj.uses: - return True - - ## Force pass for any local actions - if "@" not in obj.uses: - return True - - return False - - def fn(self, obj: Step) -> Tuple[bool, str]: - """Enforces all Actions to be pinned in a specific way. - - Pinning external Action hashes prevents unknown updates that could - break the pipelines or be the entry point to a supply chain attack. - - Pinning internal Actions to branches allow for less updates as work - is done on those repos. This is mainly to support our Action - monorepo architecture of our Actions. - - Example: - - name: Checkout Branch - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Test Bitwarden Action - uses: bitwarden/gh-actions/get-keyvault-secrets@main - - - name: Test Local Action - uses: ./actions/test-action - - - name: Test Run Action - run: echo "test" - - In this example, 'actions/checkout' must be pinned to the full commit - of the tag while 'bitwarden/gh-actions/get-keyvault-secrets' must be - pinned to 'main'. The other two Steps will be skipped. - """ - if self.skip(obj): - return True, "" - - path, ref = obj.uses.split("@") - - if path.startswith("bitwarden/gh-actions"): - if ref == "main": - return True, "" - return False, "Please pin to main" - - try: - int(ref, 16) - except ValueError: - return False, "Please pin the action to a commit sha" - - if len(ref) != 40: - return False, "Please use the full commit sha to pin the action" - - return True, "" diff --git a/lint-workflow-v2/src/bitwarden_workflow_linter/utils.py b/lint-workflow-v2/src/bitwarden_workflow_linter/utils.py deleted file mode 100644 index c0e3c704..00000000 --- a/lint-workflow-v2/src/bitwarden_workflow_linter/utils.py +++ /dev/null @@ -1,180 +0,0 @@ -"""Module of a collection of random utilities.""" - -import importlib.resources -import json -import os -import sys - -from dataclasses import dataclass -from enum import Enum -from typing import Optional, Self, TypeVar - -from ruamel.yaml import YAML - - -yaml = YAML() - - -@dataclass -class Colors: - """Class containing color codes for printing strings to output.""" - - black = "30m" - red = "31m" - green = "32m" - yellow = "33m" - blue = "34m" - magenta = "35m" - cyan = "36m" - white = "37m" - - -@dataclass -class LintLevel: - """Class to contain the numeric level and color of linting.""" - - code: int - color: Colors - - -class LintLevels(LintLevel, Enum): - """Collection of the different types of LintLevels available.""" - - NONE = 0, Colors.white - WARNING = 1, Colors.yellow - ERROR = 2, Colors.red - - -class LintFinding: - """Represents a problem detected by linting.""" - - def __init__(self, description: str, level: LintLevels) -> None: - self.description = description - self.level = level - - def __str__(self) -> str: - """String representation of the class. - - Returns: - String representation of itself. - """ - return ( - f"\033[{self.level.color}{self.level.name.lower()}\033[0m " - f"{self.description}" - ) - - -@dataclass -class Action: - """Collection of the metadata associated with a GitHub Action.""" - - name: str - version: str = "" - sha: str = "" - - def __eq__(self, other: Self) -> bool: - """Override Action equality. - - Args: - other: - Another Action type object to compare - - Return - The state of equality - """ - return ( - self.name == other.name - and self.version == other.version - and self.sha == other.sha - ) - - def __ne__(self, other: Self) -> bool: - """Override Action unequality. - - Args: - other: - Another Action type object to compare - - Return - The negation of the state of equality - """ - return not self.__eq__(other) - - -class SettingsError(Exception): - """Custom Exception to indicate an error with loading Settings.""" - - pass - - -SettingsFromFactory = TypeVar("SettingsFromFactory", bound="Settings") - - -class Settings: - """Class that contains configuration-as-code for any portion of the app.""" - - enabled_rules: list[str] - approved_actions: dict[str, Action] - - def __init__( - self, - enabled_rules: Optional[list[str]] = None, - approved_actions: Optional[dict[str, dict[str, str]]] = None, - ) -> None: - """Settings object that can be overridden in settings.py. - - Args: - enabled_rules: - All of the python modules that implement a Rule to be run against - the workflows. These must be available somewhere on the PYTHONPATH - approved_actions: - The colleciton of GitHub Actions that are pre-approved to be used - in any workflow (Required by src.rules.step_approved) - """ - if enabled_rules is None: - enabled_rules = [] - - if approved_actions is None: - approved_actions = {} - - self.enabled_rules = enabled_rules - self.approved_actions = { - name: Action(**action) for name, action in approved_actions.items() - } - - @staticmethod - def factory() -> SettingsFromFactory: - with ( - importlib.resources.files("bitwarden_workflow_linter") - .joinpath("default_settings.yaml") - .open("r", encoding="utf-8") as file - ): - settings = yaml.load(file) - - settings_filename = "settings.yaml" - local_settings = None - - if os.path.exists(settings_filename): - with open(settings_filename, encoding="utf8") as settings_file: - local_settings = yaml.load(settings_file) - - if local_settings: - settings.update(local_settings) - - if settings["approved_actions_path"] == "default_actions.json": - with ( - importlib.resources.files("bitwarden_workflow_linter") - .joinpath("default_actions.json") - .open("r", encoding="utf-8") as file - ): - settings["approved_actions"] = json.load(file) - else: - with open( - settings["approved_actions_path"], "r", encoding="utf8" - ) as action_file: - settings["approved_actions"] = json.load(action_file) - - return Settings( - enabled_rules=settings["enabled_rules"], - approved_actions=settings["approved_actions"], - ) diff --git a/lint-workflow-v2/tests/__init__.py b/lint-workflow-v2/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lint-workflow-v2/tests/conftest.py b/lint-workflow-v2/tests/conftest.py deleted file mode 100644 index af48cf42..00000000 --- a/lint-workflow-v2/tests/conftest.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Shared configuration for tests.""" - -FIXTURE_DIR = "./tests/fixtures" diff --git a/lint-workflow-v2/tests/fixtures/test-alt.yml b/lint-workflow-v2/tests/fixtures/test-alt.yml deleted file mode 100644 index 57822a21..00000000 --- a/lint-workflow-v2/tests/fixtures/test-alt.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Lint Test File, DO NOT USE - -on: - workflow_dispatch: - inputs: {} - -jobs: - test-normal-action: - name: Download Latest - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - run: | - echo test - - test-local-action: - name: Testing a local action call - runs-on: ubuntu-20.04 - steps: - - name: local-action - uses: ./version-bump diff --git a/lint-workflow-v2/tests/fixtures/test-min-incorrect.yaml b/lint-workflow-v2/tests/fixtures/test-min-incorrect.yaml deleted file mode 100644 index 5ef34058..00000000 --- a/lint-workflow-v2/tests/fixtures/test-min-incorrect.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-latest - steps: - - run: echo test diff --git a/lint-workflow-v2/tests/fixtures/test-min.yaml b/lint-workflow-v2/tests/fixtures/test-min.yaml deleted file mode 100644 index a641f914..00000000 --- a/lint-workflow-v2/tests/fixtures/test-min.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Test Workflow - -on: - workflow_dispatch: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test diff --git a/lint-workflow-v2/tests/fixtures/test.yml b/lint-workflow-v2/tests/fixtures/test.yml deleted file mode 100644 index ca1af310..00000000 --- a/lint-workflow-v2/tests/fixtures/test.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: crowdin Pull - -on: - workflow_dispatch: - inputs: {} - schedule: - - cron: "0 0 * * 5" - -jobs: - crowdin-pull: - name: Pull - runs-on: ubuntu-20.04 - env: - _CROWDIN_PROJECT_ID: "308189" - steps: - - name: Checkout repo - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v2.3.4 - - - - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve GitHub PAT secrets - id: retrieve-secret-pat - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: "bitwarden-ci" - secrets: "crowdin-api-token" - - - uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} - with: - config: crowdin.yml - crowdin_branch_name: master - upload_sources: false - upload_translations: false - download_translations: true - github_user_name: "github-actions" - github_user_email: "<>" - commit_message: "Autosync the updated translations" - localization_branch_name: crowdin-auto-sync - create_pull_request: true - pull_request_title: "Autosync Crowdin Translations" - pull_request_body: "Autosync the updated translations" diff --git a/lint-workflow-v2/tests/fixtures/test_a.yaml b/lint-workflow-v2/tests/fixtures/test_a.yaml deleted file mode 100644 index bd0cfb24..00000000 --- a/lint-workflow-v2/tests/fixtures/test_a.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Lint Test File, DO NOT USE - -on: - workflow_dispatch: - inputs: {} - -jobs: - call-workflow: - uses: bitwarden/server/.github/workflows/workflow-linter.yml@master - - test-normal-action: - name: Download Latest - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - run: | - echo test - - test-local-action: - name: Testing a local action call - runs-on: ubuntu-20.04 - steps: - - name: local-action - uses: ./version-bump diff --git a/lint-workflow-v2/tests/rules/__init__.py b/lint-workflow-v2/tests/rules/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lint-workflow-v2/tests/rules/test_job_environment_prefix.py b/lint-workflow-v2/tests/rules/test_job_environment_prefix.py deleted file mode 100644 index 89ae7d5c..00000000 --- a/lint-workflow-v2/tests/rules/test_job_environment_prefix.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Test src/bitwarden_workflow_linter/rules/job_environment_prefix.""" - -import pytest - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.rules.job_environment_prefix import ( - RuleJobEnvironmentPrefix, -) - -yaml = YAML() - - -@pytest.fixture(name="correct_workflow") -def fixture_correct_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - env: - _TEST_ENV: "test" - steps: - - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="no_env_workflow") -def fixture_no_env_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="missing_prefix_workflow") -def fixture_missing_prefix_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - env: - TEST_ENV: "test" - steps: - - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="rule") -def fixture_rule(): - return RuleJobEnvironmentPrefix() - - -def test_rule_on_correct_workflow(rule, correct_workflow): - obj = correct_workflow.jobs["job-key"] - - result, message = rule.fn(correct_workflow.jobs["job-key"]) - assert result is True - assert message == "" - - finding = rule.execute(obj) - assert finding is None - - -def test_rule_on_no_env_workflow(rule, no_env_workflow): - obj = no_env_workflow.jobs["job-key"] - - result, message = rule.fn(no_env_workflow.jobs["job-key"]) - assert result is True - assert message == "" - - finding = rule.execute(obj) - assert finding is None - - -def test_rule_on_missing_prefix_workflow(rule, missing_prefix_workflow): - obj = missing_prefix_workflow.jobs["job-key"] - - result, message = rule.fn(obj) - assert result is False - assert "TEST_ENV" in message - - finding = rule.execute(obj) - assert "TEST_ENV" in finding.description - - -def test_fail_compatibility(rule, correct_workflow): - finding = rule.execute(correct_workflow) - assert "Workflow not compatible with" in finding.description - - finding = rule.execute(correct_workflow.jobs["job-key"].steps[0]) - assert "Step not compatible with" in finding.description diff --git a/lint-workflow-v2/tests/rules/test_name_capitalized.py b/lint-workflow-v2/tests/rules/test_name_capitalized.py deleted file mode 100644 index 1e573b5b..00000000 --- a/lint-workflow-v2/tests/rules/test_name_capitalized.py +++ /dev/null @@ -1,107 +0,0 @@ -"""Test src/bitwarden_workflow_linter/rules/name_capitalized.py.""" - -import pytest - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.rules.name_capitalized import RuleNameCapitalized - -yaml = YAML() - - -@pytest.fixture(name="correct_workflow") -def fixture_correct_workflow(): - workflow = """\ ---- -name: Test Workflow - -on: - workflow_dispatch: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="incorrect_workflow") -def fixture_incorrect_workflow(): - workflow = """\ ---- -name: test -on: - workflow_dispatch: - -jobs: - job-key: - name: test - runs-on: ubuntu-latest - steps: - - name: test - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="missing_name_workflow") -def fixture_missing_name_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-latest - steps: - - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="rule") -def fixture_rule(): - return RuleNameCapitalized() - - -def test_rule_on_correct_workflow(rule, correct_workflow): - result, _ = rule.fn(correct_workflow) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[0]) - assert result is True - - -def test_rule_on_incorrect_workflow_name(rule, incorrect_workflow): - result, _ = rule.fn(incorrect_workflow) - assert result is False - - -def test_rule_on_incorrect_job_name(rule, incorrect_workflow): - result, _ = rule.fn(incorrect_workflow.jobs["job-key"]) - assert result is False - - -def test_rule_on_incorrect_step_name(rule, incorrect_workflow): - result, _ = rule.fn(incorrect_workflow.jobs["job-key"].steps[0]) - assert result is False - - -def test_rule_on_missing_names(rule, missing_name_workflow): - result, _ = rule.fn(missing_name_workflow) - assert result is True - - result, _ = rule.fn(missing_name_workflow.jobs["job-key"]) - assert result is True - - result, _ = rule.fn(missing_name_workflow.jobs["job-key"].steps[0]) - assert result is True diff --git a/lint-workflow-v2/tests/rules/test_name_exists.py b/lint-workflow-v2/tests/rules/test_name_exists.py deleted file mode 100644 index 02048dad..00000000 --- a/lint-workflow-v2/tests/rules/test_name_exists.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Test src/bitwarden_workflow_linter/rules/name_exists.py.""" - -import pytest - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.rules.name_exists import RuleNameExists - - -yaml = YAML() - - -@pytest.fixture(name="correct_workflow") -def fixture_correct_workflow(): - workflow = """\ ---- -name: Test Workflow - -on: - workflow_dispatch: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="incorrect_workflow") -def fixture_incorrect_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-latest - steps: - - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="rule") -def fixture_rule(): - return RuleNameExists() - - -def test_rule_on_correct_workflow(rule, correct_workflow): - result, _ = rule.fn(correct_workflow) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[0]) - assert result is True - - -def test_rule_on_incorrect_workflow(rule, incorrect_workflow): - print(f"Workflow name: {incorrect_workflow.name}") - result, _ = rule.fn(incorrect_workflow) - assert result is False - - result, _ = rule.fn(incorrect_workflow.jobs["job-key"]) - assert result is False - - result, _ = rule.fn(incorrect_workflow.jobs["job-key"].steps[0]) - assert result is False diff --git a/lint-workflow-v2/tests/rules/test_pinned_job_runner.py b/lint-workflow-v2/tests/rules/test_pinned_job_runner.py deleted file mode 100644 index 5db43d0a..00000000 --- a/lint-workflow-v2/tests/rules/test_pinned_job_runner.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Test src/bitwarden_workflow_linter/rules/pinned_job_runner.py.""" - -import pytest - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.rules.pinned_job_runner import ( - RuleJobRunnerVersionPinned, -) - -yaml = YAML() - - -@pytest.fixture(name="correct_runner") -def fixture_correct_runner(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - run: echo test - - call-workflow: - uses: bitwarden/server/.github/workflows/workflow-linter.yml@master -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="incorrect_runner") -def fixture_incorrect_runner(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-latest - steps: - - run: echo test -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="rule") -def fixture_rule(): - return RuleJobRunnerVersionPinned() - - -def test_rule_on_correct_runner(rule, correct_runner): - result, _ = rule.fn(correct_runner.jobs["job-key"]) - assert result is True - - result, _ = rule.fn(correct_runner.jobs["call-workflow"]) - assert result is True - - -def test_rule_on_incorrect_runner(rule, incorrect_runner): - result, _ = rule.fn(incorrect_runner.jobs["job-key"]) - assert result is False diff --git a/lint-workflow-v2/tests/rules/test_step_approved.py b/lint-workflow-v2/tests/rules/test_step_approved.py deleted file mode 100644 index 5f1f0942..00000000 --- a/lint-workflow-v2/tests/rules/test_step_approved.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Test src/bitwarden_workflow_linter/rules/step_approved.py.""" - -import pytest - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.rules.step_approved import RuleStepUsesApproved -from src.bitwarden_workflow_linter.utils import Settings - - -yaml = YAML() - - -@pytest.fixture(name="settings") -def fixture_settings(): - return Settings( - approved_actions={ - "actions/checkout": { - "name": "actions/checkout", - "version": "v4.1.1", - "sha": "b4ffde65f46336ab88eb53be808477a3936bae11", - }, - "actions/download-artifact": { - "name": "actions/download-artifact", - "version": "v4.1.0", - "sha": "f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110", - }, - } - ) - - -@pytest.fixture(name="correct_workflow") -def fixture_correct_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - name: Checkout Branch - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Test Bitwarden Action - uses: bitwarden/gh-actions/get-keyvault-secrets@main - - - name: Test Local Action - uses: ./actions/test-action - - - name: Test Run Action - run: echo "test" -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="incorrect_workflow") -def fixture_incorrect_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - name: Checkout Branch - uses: joseph-flinn/action-DNE@main - - - name: Out of date action - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="rule") -def fixture_rule(settings): - return RuleStepUsesApproved(settings=settings) - - -def test_rule_on_correct_workflow(rule, correct_workflow): - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[0]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[1]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[2]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[3]) - assert result is True - - -def test_rule_on_incorrect_workflow(rule, incorrect_workflow): - result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[0]) - assert result is False - assert "New Action detected" in message - - result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[1]) - assert result is False - assert "Action is out of date" in message - - -def test_fail_compatibility(rule, correct_workflow): - finding = rule.execute(correct_workflow) - assert "Workflow not compatible with" in finding.description - - finding = rule.execute(correct_workflow.jobs["job-key"]) - assert "Job not compatible with" in finding.description diff --git a/lint-workflow-v2/tests/rules/test_step_pinned.py b/lint-workflow-v2/tests/rules/test_step_pinned.py deleted file mode 100644 index b73e0c58..00000000 --- a/lint-workflow-v2/tests/rules/test_step_pinned.py +++ /dev/null @@ -1,104 +0,0 @@ -"""Test src/bitwarden_workflow_linter/rules/step_pinned.py.""" - -import pytest - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.rules.step_pinned import RuleStepUsesPinned - -yaml = YAML() - - -@pytest.fixture(name="correct_workflow") -def fixture_correct_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - name: Test 3rd Party Action - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Test Internal Action - uses: bitwarden/gh-actions/get-keyvault-secrets@main - - - name: Test Local Action - uses: ./actions/test-action - - - name: Test Run Action - run: echo "test" -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="incorrect_workflow") -def fixture_incorrect_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-22.04 - steps: - - name: Test External Branch - uses: actions/checkout@main - - - name: Test Incorrect Hex - uses: actions/checkout@b4ffde - - - name: Test Internal Commit - uses: bitwarden/gh-actions/get-keyvault-secrets@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="rule") -def fixture_rule(): - return RuleStepUsesPinned() - - -def test_rule_on_correct_workflow(rule, correct_workflow): - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[0]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[1]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[2]) - assert result is True - - result, _ = rule.fn(correct_workflow.jobs["job-key"].steps[3]) - assert result is True - - -def test_rule_on_incorrect_workflow_external_branch(rule, incorrect_workflow): - result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[0]) - assert result is False - assert "Please pin the action" in message - - -def test_rule_on_incorrect_workflow_hex(rule, incorrect_workflow): - result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[1]) - assert result is False - assert "Please use the full commit sha" in message - - -def test_rule_on_incorrect_workflow_internal_commit(rule, incorrect_workflow): - result, message = rule.fn(incorrect_workflow.jobs["job-key"].steps[2]) - assert result is False - assert "Please pin to main" in message - - -def test_fail_compatibility(rule, correct_workflow): - finding = rule.execute(correct_workflow) - assert "Workflow not compatible with" in finding.description - - finding = rule.execute(correct_workflow.jobs["job-key"]) - assert "Job not compatible with" in finding.description diff --git a/lint-workflow-v2/tests/test_job.py b/lint-workflow-v2/tests/test_job.py deleted file mode 100644 index f0a91105..00000000 --- a/lint-workflow-v2/tests/test_job.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Test src/bitwarden_workflow_linter/models/job.py.""" - -import pytest - -from ruamel.yaml import YAML -from ruamel.yaml.comments import CommentedMap - -from src.bitwarden_workflow_linter.models.job import Job -from src.bitwarden_workflow_linter.models.step import Step - - -yaml = YAML() - - -@pytest.fixture(name="workflow_yaml") -def fixture_workflow_yaml(): - return yaml.load( - """\ ---- -name: test -on: - workflow_dispatch: - pull_request: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test - - call-workflow: - uses: bitwarden/server/.github/workflows/workflow-linter.yml@master - - test-normal-action: - name: Download Latest - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - run: | - echo test - - test-local-action: - name: Testing a local action call - runs-on: ubuntu-20.04 - steps: - - name: local-action - uses: ./version-bump -""" - ) - - -def test_job_default(workflow_yaml): - default_job_data = workflow_yaml["jobs"]["job-key"] - default_job = Job.init("default-job", default_job_data) - - assert default_job.key == "default-job" - assert default_job.name == "Test" - assert default_job.runs_on == "ubuntu-latest" - assert default_job.env is None - assert len(default_job.steps) == 1 - - -def test_uses_job(workflow_yaml): - call_job_data = workflow_yaml["jobs"]["call-workflow"] - call_job = Job.init("call-job", call_job_data) - - assert call_job.key == "call-job" - assert call_job.uses is not None - - -def test_job_extra_kwargs(workflow_yaml): - extra_data_job = workflow_yaml["jobs"]["job-key"] - extra_data_job["extra"] = "This should not exist" - - job = Job.init("job-key", extra_data_job) - - with pytest.raises(Exception): - assert job.extra == "test" diff --git a/lint-workflow-v2/tests/test_lint.py b/lint-workflow-v2/tests/test_lint.py deleted file mode 100644 index 68ce399c..00000000 --- a/lint-workflow-v2/tests/test_lint.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Test src/bitwarden_workflow_linter/lint.py.""" - -import pytest - -from src.bitwarden_workflow_linter.lint import LinterCmd -from src.bitwarden_workflow_linter.utils import Settings, LintFinding, LintLevels - - -@pytest.fixture(name="settings") -def fixture_settings(): - return Settings() - - -def test_get_max_error_level(settings): - linter = LinterCmd(settings=settings) - - assert ( - linter.get_max_error_level( - [ - LintFinding(description="", level=LintLevels.WARNING), - LintFinding(description="", level=LintLevels.WARNING), - ] - ) - == 1 - ) - - assert ( - linter.get_max_error_level( - [ - LintFinding(description="", level=LintLevels.ERROR), - LintFinding(description="", level=LintLevels.ERROR), - ] - ) - == 2 - ) - - assert ( - linter.get_max_error_level( - [ - LintFinding(description="", level=LintLevels.ERROR), - LintFinding(description="", level=LintLevels.ERROR), - LintFinding(description="", level=LintLevels.WARNING), - LintFinding(description="", level=LintLevels.WARNING), - ] - ) - == 2 - ) diff --git a/lint-workflow-v2/tests/test_load.py b/lint-workflow-v2/tests/test_load.py deleted file mode 100644 index cedc2017..00000000 --- a/lint-workflow-v2/tests/test_load.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Tests src/bitwarden_workflow_linter/load.py.""" - -import pytest - -from ruamel.yaml import YAML -from ruamel.yaml.comments import CommentedMap - -from .conftest import FIXTURE_DIR - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.models.workflow import Workflow - - -yaml = YAML() - - -@pytest.fixture(name="workflow_filename") -def fixture_workflow_filename(): - return f"{FIXTURE_DIR}/test.yml" - - -@pytest.fixture(name="simple_workflow_yaml") -def fixture_simple_workflow_yaml(): - return yaml.load( - """\ ---- -name: test -on: - workflow_dispatch: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test -""" - ) - - -@pytest.fixture(name="complex_workflow_yaml") -def fixture_complex_workflow_yaml(): - return yaml.load( - """\ ---- -name: test -on: - workflow_dispatch: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test - - call-workflow: - uses: bitwarden/server/.github/workflows/workflow-linter.yml@master - - test-normal-action: - name: Download Latest - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - run: | - echo test - - test-local-action: - name: Testing a local action call - runs-on: ubuntu-20.04 - steps: - - name: local-action - uses: ./version-bump -""" - ) - - -def test_load_workflow_from_file(workflow_filename: str) -> None: - workflow = WorkflowBuilder.build(workflow_filename) - assert isinstance(workflow, Workflow) - - -def test_load_simple_workflow_from_yaml(simple_workflow_yaml: CommentedMap) -> None: - workflow = WorkflowBuilder.build(workflow=simple_workflow_yaml, from_file=False) - assert isinstance(workflow, Workflow) - - -def test_load_complex_workflow_from_yaml(complex_workflow_yaml: CommentedMap) -> None: - workflow = WorkflowBuilder.build(workflow=complex_workflow_yaml, from_file=False) - assert isinstance(workflow, Workflow) diff --git a/lint-workflow-v2/tests/test_rule.py b/lint-workflow-v2/tests/test_rule.py deleted file mode 100644 index 44dba8c5..00000000 --- a/lint-workflow-v2/tests/test_rule.py +++ /dev/null @@ -1,140 +0,0 @@ -"""Tests src/bitwarden_workflow_linter/rule.py.""" - -import pytest -from typing import Union - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.load import WorkflowBuilder -from src.bitwarden_workflow_linter.models.job import Job -from src.bitwarden_workflow_linter.models.step import Step -from src.bitwarden_workflow_linter.models.workflow import Workflow -from src.bitwarden_workflow_linter.rule import Rule, RuleExecutionException - - -yaml = YAML() - - -@pytest.fixture(name="correct_workflow") -def fixture_correct_workflow(): - workflow = """\ ---- -name: Test Workflow - -on: - workflow_dispatch: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - uses: actions/checkout@main -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -@pytest.fixture(name="incorrect_workflow") -def fixture_incorrect_workflow(): - workflow = """\ ---- -on: - workflow_dispatch: - -jobs: - job-key: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main -""" - return WorkflowBuilder.build(workflow=yaml.load(workflow), from_file=False) - - -class RuleStep(Rule): - def __init__(self): - self.message = "test" - self.on_fail = "error" - self.compatibility = [Step] - - -class RuleNameExists(Rule): - def __init__(self): - self.message = "name must exist" - self.on_fail = "error" - - def fn(self, obj: Union[Workflow, Job, Step]) -> bool: - print(f"{type(self).__name__}\n{obj}") - return obj.name is not None, self.message - - -class RuleException(Rule): - def __init__(self): - self.message = "should raise Exception" - self.on_fail = "error" - - def fn(self, obj: Union[Workflow, Job, Step]) -> bool: - raise RuleExecutionException("test Exception") - - -@pytest.fixture(name="step_rule") -def fixture_step_rule(): - return RuleStep() - - -@pytest.fixture(name="exists_rule") -def fixture_exists_rule(): - return RuleNameExists() - - -@pytest.fixture(name="exception_rule") -def fixture_exception_rule(): - return RuleException() - - -def test_build_lint_message(step_rule, correct_workflow): - assert step_rule.build_lint_message("test", correct_workflow) == "Workflow => test" - - assert ( - step_rule.build_lint_message("test", correct_workflow.jobs["job-key"]) - == "Job [job-key] => test" - ) - - assert ( - step_rule.build_lint_message("test", correct_workflow.jobs["job-key"].steps[0]) - == "Step [job-key.0] => test" - ) - - -def test_rule_compatibility(step_rule, correct_workflow): - assert "not compatible" in step_rule.execute(correct_workflow).description - assert ( - "not compatible" - in step_rule.execute(correct_workflow.jobs["job-key"]).description - ) - assert ( - "not compatible" - not in step_rule.execute(correct_workflow.jobs["job-key"].steps[0]).description - ) - - -def test_correct_rule_execution(exists_rule, correct_workflow): - assert exists_rule.execute(correct_workflow) is None - assert exists_rule.execute(correct_workflow.jobs["job-key"]) is None - assert exists_rule.execute(correct_workflow.jobs["job-key"].steps[0]) is None - - -def test_incorrect_rule_execution(exists_rule, incorrect_workflow): - assert "name must exist" in exists_rule.execute(incorrect_workflow).description - assert ( - "name must exist" - in exists_rule.execute(incorrect_workflow.jobs["job-key"]).description - ) - assert ( - "name must exist" - in exists_rule.execute(incorrect_workflow.jobs["job-key"].steps[0]).description - ) - - -def test_exception_rule_execution(exception_rule, incorrect_workflow): - assert "failed to apply" in exception_rule.execute(incorrect_workflow).description diff --git a/lint-workflow-v2/tests/test_step.py b/lint-workflow-v2/tests/test_step.py deleted file mode 100644 index 23625d16..00000000 --- a/lint-workflow-v2/tests/test_step.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Test src/bitwarden_workflow_linter/models/step.py.""" - -import json -import pytest - -from ruamel.yaml import YAML - -from src.bitwarden_workflow_linter.models.step import Step - - -@pytest.fixture(name="default_step") -def fixture_default_step(): - step_str = """\ -name: Default Step -run: echo "test" -""" - yaml = YAML() - step_yaml = yaml.load(step_str) - return Step.init(0, "default", step_yaml) - - -@pytest.fixture(name="uses_step") -def fixture_uses_step(): - step_str = """\ -name: Download Artifacts -uses: bitwarden/download-artifacts@main # v1.0.0 -with: - workflow: upload-test-artifacts.yml - artifacts: artifact - path: artifact - branch: main - -""" - yaml = YAML() - step_yaml = yaml.load(step_str) - return Step.init(0, "default", step_yaml) - - -def test_step_default(default_step): - assert default_step.key == 0 - assert default_step.job == "default" - assert default_step.name == "Default Step" - assert default_step.env is None - assert default_step.uses is None - assert default_step.uses_with is None - assert default_step.run == 'echo "test"' - - -def test_step_no_keyword_field(default_step): - assert default_step.uses_with is None - assert "uses_with" not in default_step.to_json() - - -def test_step_extra_kwargs(default_step): - with pytest.raises(Exception): - assert default_step.extra == "test" - - -def test_step_keyword_field(uses_step): - expected_response = { - "workflow": "upload-test-artifacts.yml", - "artifacts": "artifact", - "path": "artifact", - "branch": "main", - } - - step_json = uses_step.to_json() - assert uses_step.key == 0 - assert "uses_with" not in step_json - assert "with" in step_json - assert json.loads(uses_step.to_json())["with"] == expected_response - - -def test_step_comment(uses_step): - assert uses_step.key == 0 - assert uses_step.job == "default" - assert uses_step.uses_comment is not None - assert uses_step.uses_comment == "# v1.0.0" diff --git a/lint-workflow-v2/tests/test_utils.py b/lint-workflow-v2/tests/test_utils.py deleted file mode 100644 index baf75d2d..00000000 --- a/lint-workflow-v2/tests/test_utils.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Tests src/bitwarden_workflow_linter/utils.py.""" - -from src.bitwarden_workflow_linter.utils import Action, Colors, LintFinding, LintLevels - - -def test_action_eq(): - action_def = {"name": "bitwarden/sm-action", "version": "1.0.0", "sha": "some-sha"} - - action_a = Action(**action_def) - action_b = Action(**action_def) - - assert (action_a == action_b) is True - assert (action_a != action_b) is False - - -def test_action_ne(): - action_a = Action(name="bitwarden/sm-action", version="1.0.0", sha="some-sha") - action_b = Action(name="bitwarden/sm-action", version="1.1.0", sha="some-other-sha") - - assert (action_a == action_b) is False - assert (action_a != action_b) is True - - -def test_lint_level(): - warning = LintLevels.WARNING - assert warning.code == 1 - assert warning.color == Colors.yellow - - -def test_lint_finding(): - warning = LintFinding(description="", level=LintLevels.WARNING) - assert str(warning) == "\x1b[33mwarning\x1b[0m " - - error = LintFinding(description="", level=LintLevels.ERROR) - assert str(error) == "\x1b[31merror\x1b[0m " diff --git a/lint-workflow-v2/tests/test_workflow.py b/lint-workflow-v2/tests/test_workflow.py deleted file mode 100644 index da35f1f2..00000000 --- a/lint-workflow-v2/tests/test_workflow.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Test src/bitwarden_workflow_linter/models/workflow.py.""" - -import pytest - -from ruamel.yaml import YAML -from ruamel.yaml.comments import CommentedMap - -from src.bitwarden_workflow_linter.models.job import Job -from src.bitwarden_workflow_linter.models.step import Step -from src.bitwarden_workflow_linter.models.workflow import Workflow - - -yaml = YAML() - - -@pytest.fixture(name="simple_workflow_yaml") -def fixture_simple_workflow_yaml(): - return yaml.load( - """\ ---- -name: test -on: - workflow_dispatch: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test -""" - ) - - -@pytest.fixture(name="complex_workflow_yaml") -def fixture_complex_workflow_yaml(): - return yaml.load( - """\ ---- -name: test -on: - workflow_dispatch: - pull_request: - -jobs: - job-key: - name: Test - runs-on: ubuntu-latest - steps: - - name: Test - run: echo test - - call-workflow: - uses: bitwarden/server/.github/workflows/workflow-linter.yml@master - - test-normal-action: - name: Download Latest - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - run: | - echo test - - test-local-action: - name: Testing a local action call - runs-on: ubuntu-20.04 - steps: - - name: local-action - uses: ./version-bump -""" - ) - - -def test_simple_workflow(simple_workflow_yaml): - workflow = Workflow.init("", simple_workflow_yaml) - - assert workflow.name == "test" - assert len(workflow.on.keys()) == 1 - assert len(workflow.jobs.keys()) == 1 - - -def test_complex_workflow(complex_workflow_yaml): - workflow = Workflow.init("", complex_workflow_yaml) - - assert workflow.name == "test" - assert len(workflow.on.keys()) == 2 - assert len(workflow.jobs.keys()) == 4 - - -def test_workflow_extra_kwargs(simple_workflow_yaml): - extra_data_workflow = simple_workflow_yaml - extra_data_workflow["extra"] = "This should not exist" - - workflow = Workflow.init("", extra_data_workflow) - - with pytest.raises(Exception): - assert workflow.extra == "test" From f353d565dab1e84c9447410f3cac890c32918c9b Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Mon, 15 Jul 2024 07:58:37 -0700 Subject: [PATCH 2/2] Remove Linter V2 CI --- .github/workflows/lint-ci.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.github/workflows/lint-ci.yml b/.github/workflows/lint-ci.yml index ee3f0eb3..2b147c43 100644 --- a/.github/workflows/lint-ci.yml +++ b/.github/workflows/lint-ci.yml @@ -32,31 +32,3 @@ jobs: - name: Test lint working-directory: lint-workflow run: pipenv run pytest tests - - - ci-lint-v2: - name: CI workflow-linter (v2) - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Set up Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 - with: - python-version: "3.11" - - - name: Install dependencies - working-directory: lint-workflow-v2 - run: | - python -m pip install --upgrade pip - pip install pipenv - pipenv install --dev - - - name: Test lint - working-directory: lint-workflow-v2 - run: pipenv run pytest tests --cov=src - - - name: Check type hinting - working-directory: lint-workflow-v2 - run: pipenv run pytype src