Skip to content
Open
2 changes: 1 addition & 1 deletion .github/workflows/code-checkers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
uv run semgrep scan \
--config p/default \
--config p/security-audit \
--config semgrep.yml \
--config .semgrep \
--error \
--exclude-rule python.flask.security.audit.directly-returned-format-string.directly-returned-format-string \
--exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
Expand Down
14 changes: 14 additions & 0 deletions .semgrep/python-enforce-class-pascal-case.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
rules:
- id: python-enforce-class-pascal-case
languages: [python]
severity: WARNING
message: "Class `$CLASS` must respect PascalCase style."

patterns:
- pattern: |
class $CLASS(...):
...

- metavariable-regex:
metavariable: $CLASS
regex: '^(?!_?([A-Z][a-z0-9]+)+$)'
30 changes: 30 additions & 0 deletions .semgrep/python-enforce-enum-uppercase-members.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
rules:
- id: python-enforce-enum-uppercase-members
languages: [python]
severity: WARNING
message: "Enum member `$MEMBER` must respect UPPER_CASE style."
patterns:
- pattern-either:
- pattern: "$MEMBER = $VAL"
- pattern: "$MEMBER: $TYPE = $VAL"

- metavariable-regex:
metavariable: $MEMBER
regex: '^(?![A-Z][A-Z0-9_]*$)'

- pattern-either:
- pattern-inside: |
class $ENUM(..., Enum, ...):
...
- pattern-inside: |
class $ENUM(..., IntEnum, ...):
...
- pattern-inside: |
class $ENUM(..., StrEnum, ...):
...
- pattern-inside: |
class $FLAG(..., Flag, ...):
...
- pattern-inside: |
class $FLAG(..., IntFlag, ...):
...
17 changes: 17 additions & 0 deletions .semgrep/python-enforce-exec-path-prefix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
rules:
- id: python-enforce-exec-path-prefix
languages: [python]
severity: WARNING
message: "Executable path variable must use `EXEC_PATH` or `_EXEC_PATH` prefix."
patterns:
- pattern-either:
- pattern: "$VAR = \"$PATH\""
- pattern: "$VAR: $TYPE = \"$PATH\""

- metavariable-regex:
metavariable: $PATH
regex: "^/(usr/)?(bin|sbin)/.+"

- metavariable-regex:
metavariable: $VAR
regex: "^(?!(?:_)?EXEC_PATH)"
39 changes: 39 additions & 0 deletions .semgrep/python-enforce-file-variable-suffix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
rules:
- id: python-enforce-file-variable-suffix
languages: [python]
severity: WARNING
message: "File object `$FILE` must be named `file` or end with `_file`."
patterns:
- pattern-either:
- pattern: |
with open(...) as $FILE:
...
- pattern: |
with ..., open(...) as $FILE, ...:
...
- pattern: |
with Path(...).open(...) as $FILE:
...
- pattern: |
with ..., Path(...).open(...) as $FILE, ...:
...
- pattern: $FILE = open(...)
- pattern: $FILE = Path(...).open(...)
- patterns:
- pattern-either:
- pattern: |
with $PATH.open(...) as $FILE:
...
- pattern: |
with ..., $PATH.open(...) as $FILE, ...:
...
- pattern: $FILE = $PATH.open(...)
- metavariable-regex:
metavariable: $PATH
regex: '^(.*[._])?path$'

- focus-metavariable: $FILE

- metavariable-regex:
metavariable: $FILE
regex: '^(?!(file|\w+_file)$)'
13 changes: 13 additions & 0 deletions .semgrep/python-forbid-direct-logging-import.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
rules:
- id: python-forbid-direct-logging-import
languages: [python]
severity: WARNING
message: "Import from `xcp_storage.log`, not from `logging`."
paths:
exclude:
- src/xcp_storage/log.py
pattern-either:
- pattern: import logging
- pattern: import logging.$X
- pattern: from logging import $X
- pattern: from logging.$X import $Y
13 changes: 13 additions & 0 deletions .semgrep/python-forbid-direct-typing-import.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
rules:
- id: python-forbid-direct-typing-import
languages: [python]
severity: WARNING
message: "Import from `xcp_storage.typing`, not from `typing` or `typing_extensions`."
paths:
exclude:
- src/xcp_storage/typing/
pattern-either:
- pattern: from typing import $X
- pattern: from typing_extensions import $X
- pattern: import typing
- pattern: import typing_extensions
19 changes: 19 additions & 0 deletions .semgrep/python-forbid-import-of-from-only-modules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
rules:
- id: python-forbid-import-of-from-only-modules
languages: [python]
severity: WARNING
message: "This module is always imported with `from X import ...`, not with `import X`."
pattern-regex: |-
(?x)
^[ \t]*import[ \t]+(
abc
| cryptography(\.\w+)*
| dataclasses
| enum
| functools
| pathlib
| types
| tests(\.\w+)+
| unittest\.mock
| xcp_storage\.(?!log\b)(?!rpc\.(api|modules)\b)\w+(\.\w+)*
)\b
26 changes: 26 additions & 0 deletions .semgrep/python-forbid-subprocess-usage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
rules:
- id: python-forbid-subprocess-usage
languages: [python]
severity: WARNING
message: "Run commands with `run_command` from `xcp_storage.utils.process`, not directly with `subprocess` or `os`."
paths:
include:
- src/
exclude:
- src/xcp_storage/utils/process.py
pattern-either:
- pattern: import subprocess
- pattern: import subprocess.$X
- pattern: from subprocess import $X
- pattern: from subprocess.$X import $Y
- pattern: pty.spawn(...)
- patterns:
- pattern: os.$FUNC(...)
- metavariable-regex:
metavariable: $FUNC
regex: '^(system|popen|exec.*|spawn.*|posix_spawn.*)$'
- patterns:
- pattern: asyncio.$FUNC(...)
- metavariable-regex:
metavariable: $FUNC
regex: '^create_subprocess_(exec|shell)$'
File renamed without changes.
37 changes: 37 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,43 @@ section-order = [
"typing"
]

[tool.ruff.lint.flake8-import-conventions]
# Modules that are always imported with `import X`, never with `from X import ...`.
banned-from = [
"asyncio",
"contextlib",
"datetime",
"errno",
"fcntl",
"inspect",
"ipaddress",
"json",
"logging",
"logging.handlers",
"multiprocessing",
"multiprocessing.synchronize",
"os",
"pytest",
"re",
"select",
"signal",
"socket",
"ssl",
"struct",
"subprocess",
"sys",
"threading",
"time",
"uuid"
]

[tool.ruff.lint.flake8-import-conventions.extend-aliases]
# Modules that are always imported with `import X as Y`.
"xcp_storage.log" = "log"
"xcp_storage.rpc.api" = "api"
"xcp_storage.rpc.modules.drbd" = "drbd"
"xcp_storage.rpc.modules.echo" = "echo"

[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

Expand Down
2 changes: 1 addition & 1 deletion src/xcp_storage/rpc/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import ssl

# Import API mods to ensure ApiDispatcher is exported with all public methods.
import xcp_storage.rpc.api # noqa: F401
import xcp_storage.rpc.api # noqa: F401, ICN001
from xcp_storage.rpc.dispatcher import ApiDispatcher
from xcp_storage.utils.json.rpc.server import JsonRpcServer

Expand Down
Loading