Skip to content

Commit 4e89638

Browse files
committed
feat(CLI): add command to run analysis inside a prebuilt docker container
1 parent 0cebda7 commit 4e89638

File tree

5 files changed

+332
-1
lines changed

5 files changed

+332
-1
lines changed

codesectools/cli.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import os
8+
import shutil
89
from pathlib import Path
910
from typing import Optional
1011

@@ -201,5 +202,19 @@ def download(
201202

202203
cli.add_typer(build_all_sast_cli())
203204

205+
if shutil.which("docker"):
206+
207+
@cli.command()
208+
def docker(
209+
target: Annotated[Path, typer.Option()] = Path("."),
210+
isolation: Annotated[bool, typer.Option()] = False,
211+
) -> None:
212+
"""Start the Docker environment for the specified target (current directory by default)."""
213+
from codesectools.shared.docker import AnalysisEnvironment
214+
215+
env = AnalysisEnvironment(isolation=isolation)
216+
env.start(target=target.resolve())
217+
218+
204219
for _, sast_data in SASTS_ALL.items():
205220
cli.add_typer(sast_data["cli_factory"].build_cli())

codesectools/shared/docker.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Docker client wrapper module."""
2+
3+
import os
4+
import sys
5+
from hashlib import sha256
6+
from pathlib import Path
7+
8+
from python_on_whales import DockerClient
9+
from python_on_whales.client_config import ClientNotFoundError
10+
11+
from codesectools.utils import PACKAGE_DIR, USER_DIR
12+
13+
UID = os.getuid()
14+
GID = os.getgid()
15+
16+
17+
class Docker(DockerClient):
18+
"""Wrapper around DockerClient to handle initialization errors."""
19+
20+
def __init__(self) -> None:
21+
"""Initialize the Docker client and verify availability."""
22+
try:
23+
super().__init__()
24+
self.info()
25+
except ClientNotFoundError as e:
26+
print(e)
27+
sys.exit(1)
28+
29+
30+
class AnalysisEnvironment:
31+
"""Manage the Docker environment for code analysis."""
32+
33+
build_args = {"UID": str(UID), "GID": str(GID)}
34+
35+
def __init__(self, isolation: bool) -> None:
36+
"""Initialize the analysis environment."""
37+
self.isolation = isolation
38+
self.docker = Docker()
39+
self.dockerfile = PACKAGE_DIR.parent / "Dockerfile"
40+
self.name = "codesectools"
41+
42+
def build(self) -> None:
43+
"""Build the Docker image if it does not exist or if the file hash changed."""
44+
file_hash = sha256(self.dockerfile.read_bytes()).hexdigest()
45+
if not self.docker.images(
46+
all=True, filters={"label": f"file_hash={file_hash}"}
47+
):
48+
self.docker.image.build(
49+
PACKAGE_DIR.parent,
50+
tags=self.name,
51+
labels={"file_hash": file_hash},
52+
file=self.dockerfile,
53+
build_args=self.build_args,
54+
)
55+
56+
def start(self, target: Path) -> None:
57+
"""Start the Docker container and attach to it.
58+
59+
Build the image if necessary, then create and start a container
60+
with the target directory mounted. If a container for the target
61+
already exists, it starts and attaches to it.
62+
63+
Args:
64+
target: The path to the directory to mount in the container.
65+
66+
"""
67+
self.build()
68+
69+
if self.isolation:
70+
target_container_name = f"codesectools-{target.name}-isolated"
71+
else:
72+
target_container_name = f"codesectools-{target.name}"
73+
target_container_home = Path("/home/codesectools")
74+
target_container_workdir = target_container_home / target.name
75+
76+
if containers := self.docker.ps(
77+
all=True,
78+
filters=[
79+
("name", "codesectools-*"),
80+
("label", f"target={str(target.resolve())}"),
81+
("label", f"isolation={self.isolation}"),
82+
],
83+
):
84+
container = containers[0]
85+
if not container.state.running:
86+
self.docker.start(container)
87+
88+
container.execute(
89+
["/bin/bash"],
90+
interactive=True,
91+
tty=True,
92+
)
93+
else:
94+
container = self.docker.run(
95+
self.name,
96+
name=target_container_name,
97+
command=["/bin/bash"],
98+
labels={
99+
"target": str(target.resolve()),
100+
"isolation": str(self.isolation),
101+
},
102+
networks=["none"] if self.isolation else [],
103+
volumes=[
104+
(target, target_container_workdir),
105+
(USER_DIR, target_container_home / USER_DIR.name),
106+
],
107+
interactive=True,
108+
tty=True,
109+
)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies = [
1515
"lxml>=6.0.2",
1616
"matplotlib>=3.10.3",
1717
"numpy>=2.3.1",
18+
"python-on-whales>=0.79.0",
1819
"pyyaml>=6.0.2",
1920
"requests>=2.32.4",
2021
"tqdm>=4.67.1",

requirements.txt

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# This file was autogenerated by uv via the following command:
22
# uv export --frozen --output-file=requirements.txt
33
-e .
4+
annotated-types==0.7.0 \
5+
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
6+
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
7+
# via pydantic
48
certifi==2025.10.5 \
59
--hash=sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de \
610
--hash=sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43
@@ -495,6 +499,73 @@ pillow==11.3.0 \
495499
--hash=sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c \
496500
--hash=sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4
497501
# via matplotlib
502+
pydantic==2.12.5 \
503+
--hash=sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49 \
504+
--hash=sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d
505+
# via python-on-whales
506+
pydantic-core==2.41.5 \
507+
--hash=sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90 \
508+
--hash=sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740 \
509+
--hash=sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33 \
510+
--hash=sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0 \
511+
--hash=sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e \
512+
--hash=sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0 \
513+
--hash=sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34 \
514+
--hash=sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3 \
515+
--hash=sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815 \
516+
--hash=sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14 \
517+
--hash=sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375 \
518+
--hash=sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf \
519+
--hash=sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1 \
520+
--hash=sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553 \
521+
--hash=sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470 \
522+
--hash=sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2 \
523+
--hash=sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660 \
524+
--hash=sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c \
525+
--hash=sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008 \
526+
--hash=sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a \
527+
--hash=sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd \
528+
--hash=sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586 \
529+
--hash=sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869 \
530+
--hash=sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294 \
531+
--hash=sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66 \
532+
--hash=sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d \
533+
--hash=sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07 \
534+
--hash=sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36 \
535+
--hash=sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e \
536+
--hash=sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05 \
537+
--hash=sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612 \
538+
--hash=sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b \
539+
--hash=sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11 \
540+
--hash=sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd \
541+
--hash=sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c \
542+
--hash=sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a \
543+
--hash=sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf \
544+
--hash=sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858 \
545+
--hash=sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9 \
546+
--hash=sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2 \
547+
--hash=sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3 \
548+
--hash=sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc \
549+
--hash=sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23 \
550+
--hash=sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa \
551+
--hash=sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d \
552+
--hash=sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3 \
553+
--hash=sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d \
554+
--hash=sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9 \
555+
--hash=sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1 \
556+
--hash=sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56 \
557+
--hash=sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c \
558+
--hash=sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9 \
559+
--hash=sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5 \
560+
--hash=sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e \
561+
--hash=sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc \
562+
--hash=sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb \
563+
--hash=sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0 \
564+
--hash=sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69 \
565+
--hash=sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c \
566+
--hash=sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75 \
567+
--hash=sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7
568+
# via pydantic
498569
pygments==2.19.2 \
499570
--hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \
500571
--hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b
@@ -507,6 +578,10 @@ python-dateutil==2.9.0.post0 \
507578
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
508579
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
509580
# via matplotlib
581+
python-on-whales==0.79.0 \
582+
--hash=sha256:4a39ab107a4e740e4302c853c0efc5ced4b4048f55acbb959a2faf53b7003c27 \
583+
--hash=sha256:919bba304cc04db4b75cdd7fb14c0a8fea6ebeafacf7989f6956641cb58210e0
584+
# via codesectools
510585
pyyaml==6.0.3 \
511586
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
512587
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \
@@ -579,7 +654,16 @@ typer==0.20.0 \
579654
typing-extensions==4.15.0 \
580655
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
581656
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
582-
# via typer
657+
# via
658+
# pydantic
659+
# pydantic-core
660+
# python-on-whales
661+
# typer
662+
# typing-inspection
663+
typing-inspection==0.4.2 \
664+
--hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \
665+
--hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464
666+
# via pydantic
583667
urllib3==2.5.0 \
584668
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
585669
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc

0 commit comments

Comments
 (0)