-
Notifications
You must be signed in to change notification settings - Fork 6
Create lint pipeline and fix errors #586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b7a0530
2112a52
fc59e6e
9976891
8f2edd3
da848e5
07ced11
04bbeca
b8afe59
3691b8c
62e1273
047e20e
82ff965
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| name: lint | ||
| on: | ||
| push | ||
| jobs: | ||
|
|
||
| lint: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v5 | ||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: '>=1.22' | ||
| - name: Install dependencies | ||
| run: make setup-lint | ||
| - name: Run lint | ||
| run: . venv/bin/activate && make lint | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,19 +74,31 @@ setup-test-e2e: | |
| ( cd racetrack_commons && make setup ) | ||
| @echo Activate your venv: . venv/bin/activate | ||
|
|
||
| setup-lint: | ||
| uv venv venv &&\ | ||
| . venv/bin/activate &&\ | ||
| uv pip install -r requirements-test.txt -r requirements-dev.txt \ | ||
| -r racetrack_client/requirements.txt -e racetrack_client@racetrack_client \ | ||
| -r racetrack_commons/requirements.txt -e racetrack_commons@racetrack_commons \ | ||
| -r lifecycle/requirements.txt -e lifecycle@lifecycle \ | ||
| -r image_builder/requirements.txt -e image_builder@image_builder \ | ||
| -r dashboard/requirements.txt -e dashboard@dashboard | ||
| @echo Activate your venv: . venv/bin/activate | ||
|
|
||
| install-racetrack-client: | ||
| ( cd racetrack_client && pip install -e . ) | ||
|
|
||
| lint: | ||
| -python -m mypy --ignore-missing-imports --exclude 'racetrack_client/build' racetrack_client | ||
| -python -m mypy --ignore-missing-imports racetrack_commons | ||
| -python -m mypy --ignore-missing-imports --exclude 'lifecycle/lifecycle/django/registry/migrations' lifecycle | ||
| -python -m mypy --ignore-missing-imports image_builder | ||
| -python -m mypy --ignore-missing-imports dashboard | ||
| -python -m flake8 --ignore E501 --per-file-ignores="__init__.py:F401" \ | ||
| lifecycle image_builder dashboard | ||
| -python -m pylint --disable=R,C,W \ | ||
| lifecycle/lifecycle image_builder/image_builder dashboard/dashboard | ||
| python -m mypy --ignore-missing-imports --exclude 'racetrack_client/build' racetrack_client; e1=$$?;\ | ||
| python -m mypy --ignore-missing-imports racetrack_commons; e2=$$?;\ | ||
| python -m mypy --ignore-missing-imports --exclude 'lifecycle/lifecycle/django/registry/migrations' lifecycle; e3=$$?;\ | ||
| python -m mypy --ignore-missing-imports image_builder; e4=$$?;\ | ||
| python -m mypy --ignore-missing-imports dashboard; e5=$$?;\ | ||
| python -m flake8 --ignore E501 --per-file-ignores="__init__.py:F401 lifecycle/lifecycle/event_stream/server.py:E402" \ | ||
| lifecycle image_builder dashboard; e6=$$?;\ | ||
| python -m pylint --disable=R,C,W \ | ||
| lifecycle/lifecycle image_builder/image_builder dashboard/dashboard; e7=$$?;\ | ||
| exit "$$(( e1 || e2 || e3 || e4 || e5 || e6 || e7 ))" | ||
|
Comment on lines
+92
to
+101
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not enough of a Makefile guru to understand what is going on here. It seems like we previously ignored non-zero exit codes, but now we exit with the cumulative Also, does this even work? I was under the impression that Makefiles exited upon a nonzero return code. With the initial dash deleted, that's the case here, no?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This basically takes all exit codes and "or" on them will return 0 if all of them were 0, some other code if at least one was not 0. It works, but it's ugly - this is purely bash syntax used, since it's makefile maybe I could split it into multiple commands and last one dependent on them? I would need to experiment a bit.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it works then I'm not too fussed either way. Makefiles are not exactly known for their graceful syntax |
||
|
|
||
| format: | ||
| python -m black -S --diff --color -l 120 \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,17 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import base64 | ||
| import functools | ||
| import hashlib | ||
| import math | ||
| from typing import Optional | ||
| from typing import Optional, Protocol | ||
| import secrets | ||
|
|
||
| from typing import TYPE_CHECKING | ||
| if TYPE_CHECKING: | ||
| from _typeshed import ReadableBuffer | ||
|
|
||
|
|
||
| RANDOM_STRING_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | ||
|
|
||
|
|
||
|
|
@@ -14,6 +21,11 @@ def make_password(password: str) -> str: | |
| return hasher.encode(password, salt) | ||
|
|
||
|
|
||
| class Hash(Protocol): | ||
| def __call__(self, string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> hashlib._Hash: | ||
| ... | ||
|
Comment on lines
+24
to
+26
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this do except be a placeholder? If it really is only a placeholder, why?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, this one is weird. There is no type I could import from hash library that would allow me to correctly type
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh. Put a comment in about it, otherwise this just looks wrong. |
||
|
|
||
|
|
||
| class PBKDF2PasswordHasher: | ||
| """ | ||
| Secure password hashing using the PBKDF2 algorithm (recommended) | ||
|
|
@@ -25,10 +37,10 @@ class PBKDF2PasswordHasher: | |
|
|
||
| algorithm: str = "pbkdf2_sha256" | ||
| iterations: int = 600000 | ||
| digest: 'hashlib._Hash' = hashlib.sha256 | ||
| digest: Hash = hashlib.sha256 | ||
| salt_entropy: int = 128 | ||
|
|
||
| def encode(self, password: str, salt, iterations: Optional[int]=None): | ||
| def encode(self, password: str, salt, iterations: Optional[int] = None): | ||
| iterations = iterations or self.iterations | ||
| hash = pbkdf2(password, salt, iterations, digest=self.digest) | ||
| hash = base64.b64encode(hash).decode("ascii").strip() | ||
|
|
@@ -60,7 +72,7 @@ def salt(self) -> str: | |
| return get_random_string(char_count, allowed_chars=RANDOM_STRING_CHARS) | ||
|
|
||
|
|
||
| def get_random_string(length: int, allowed_chars: str=RANDOM_STRING_CHARS) -> str: | ||
| def get_random_string(length: int, allowed_chars: str = RANDOM_STRING_CHARS) -> str: | ||
| """ | ||
| Return a securely generated random string. | ||
|
|
||
|
|
@@ -74,14 +86,14 @@ def get_random_string(length: int, allowed_chars: str=RANDOM_STRING_CHARS) -> st | |
| return "".join(secrets.choice(allowed_chars) for i in range(length)) | ||
|
|
||
|
|
||
| def pbkdf2(password: str, salt: str, iterations: int, dklen: int=0, digest: Optional['hashlib._Hash']=None): | ||
| def pbkdf2(password: str, salt: str, iterations: int, dklen: int = 0, digest: Optional[Hash] = None): | ||
| """Return the hash of password using pbkdf2.""" | ||
| if digest is None: | ||
| digest = hashlib.sha256 | ||
| dklen = dklen or None | ||
| password = password.encode("utf-8", "strict") | ||
| salt = salt.encode("utf-8", "strict") | ||
| return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen) | ||
|
|
||
| password_bytes = password.encode("utf-8", "strict") | ||
| salt_bytes = salt.encode("utf-8", "strict") | ||
| return hashlib.pbkdf2_hmac(digest().name, password_bytes, salt_bytes, iterations, dklen or None) | ||
|
|
||
|
|
||
| def constant_time_compare(val1, val2): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,32 @@ | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| def validate_password(password: str): | ||
| for validator in [validate_password_length, validate_numeric_password, validate_common_password]: | ||
| validator(password) | ||
|
|
||
|
|
||
| def validate_password_length(password: str): | ||
| if len(password) < 8: | ||
| raise ValidationError("This password is too short. It must contain at least 8 characters.") | ||
|
|
||
|
|
||
|
|
||
| def validate_numeric_password(password: str): | ||
| if password.isdigit(): | ||
| raise ValidationError("This password is entirely numeric.") | ||
|
|
||
|
|
||
|
|
||
| def validate_common_password(password: str): | ||
| # password list based on https://gist.github.com/roycewilliams/226886fd01572964e1431ac8afc999ce | ||
| # by Royce Williams | ||
| passwords_path: str = Path(__file__).resolve().parent / "common-passwords.txt" | ||
| passwords_path: Path = Path(__file__).resolve().parent / "common-passwords.txt" | ||
|
|
||
| with open(passwords_path, "rt", encoding="utf-8") as f: | ||
| passwords = {x.strip() for x in f} | ||
|
|
||
| if password.lower().strip() in passwords: | ||
| raise ValidationError("This password is too common.") | ||
|
|
||
|
|
||
| class ValidationError(Exception): | ||
| pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we're pinning the python version and not the go version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I must have copy-pasted this config without thinking. In theory it should be fine since go cares about backward compatibility, but pinning makes more sense.