Skip to content

Commit 7b22c84

Browse files
authored
MAINT: library vulnerabilities action (#505)
* library vulnerabilities action * fix workflow file * address bandit issues with exclusions and code changes * whitespace in pyproject.toml * remove commented out import * address some copilot review comments
1 parent 4be2997 commit 7b22c84

File tree

15 files changed

+146
-59
lines changed

15 files changed

+146
-59
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ jobs:
6262
operating-system: ${{ matrix.os }}
6363
python-version: ${{ matrix.python-version }}
6464

65+
check-vulnerabilities:
66+
name: "Check library vulnerabilities"
67+
runs-on: ubuntu-latest
68+
steps:
69+
- uses: ansys/actions/[email protected]
70+
with:
71+
python-version: ${{ env.MAIN_PYTHON_VERSION }}
72+
token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }}
73+
python-package-name: ${{ env.PACKAGE_NAME }}
74+
dev-mode: ${{ github.ref != 'refs/heads/main' }}
75+
#upload-reports: True
76+
#hide-log: false
77+
6578
build:
6679
name: Build package, incl. API generation
6780
needs: [smoke-tests]

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ maintainers = [
1919
dependencies = [
2020
"ansys-api-systemcoupling==0.2.0",
2121
"ansys-platform-instancemanagement~=1.0",
22+
"docker>=7.1.0",
2223
"grpcio>=1.30.0",
2324
"grpcio-status>=1.30.0",
2425
"googleapis-common-protos>=1.50.0",

src/ansys/systemcoupling/core/adaptor/impl/get_syc_version.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,14 @@ def get_syc_version(api) -> str:
4444
def clean_version_string(version_in: str) -> str:
4545
year, _, release = version_in.partition(" ")
4646
if len(year) == 4 and year.startswith("20") and release.startswith("R"):
47+
# Exclude Bandit check. The try-except-pass is only used to simplify logic.
48+
# An exception will be thrown in any case, but it *also* gets thrown for
49+
# input that does not match the above 'if' condition.
4750
try:
4851
year = int(year[2:])
4952
release = int(release[1:])
5053
return f"{year}.{release}"
51-
except:
54+
except: # nosec B110
5255
pass
5356
raise RuntimeError(
5457
f"Version string {version_in} has invalid format (expect '20yy Rn')."

src/ansys/systemcoupling/core/adaptor/impl/injected_commands.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ def _ensure_file_available(session: SessionProtocol, filepath: str) -> str:
187187
file_name = os.path.basename(filepath)
188188
root_name, _, ext = file_name.rpartition(".")
189189
ext = f".{ext}" if ext else ""
190-
new_name = f"{root_name}_{int(time.time())}_{random.randint(1, 10000000)}{ext}"
190+
# Exclude Bandit check as random number is simply being used to create a unique
191+
# file name, not for security/cryptographic purposes.
192+
new_name = f"{root_name}_{int(time.time())}_{random.randint(1, 10000000)}{ext}" # nosec B311
191193

192194
session._native_api.ExecPythonString(
193195
PythonString=f"import shutil\nshutil.copy('{filepath}', '{new_name}')"

src/ansys/systemcoupling/core/charts/csv_chartdata.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
TimestepData,
3333
TransferSeriesInfo,
3434
)
35+
from ansys.systemcoupling.core.util.assertion import assert_
3536

3637
HeaderList = list[str]
3738
ChartData = list[list[float]]
@@ -231,8 +232,8 @@ def _parse_suffix(header: str, part_disp_name: str) -> str:
231232

232233
def parse_csv_metadata(interface_name: str, headers: list[str]) -> InterfaceInfo:
233234
intf_info = InterfaceInfo(name=interface_name)
234-
assert headers[0] == "Iteration"
235-
assert headers[1] == "Step"
235+
assert_(headers[0] == "Iteration", 'Header expected to be "Iteration"')
236+
assert_(headers[1] == "Step", 'Header expected to be "Step"')
236237
intf_info.is_transient = headers[2] == "Time"
237238

238239
start_index = 3 if intf_info.is_transient else 2
@@ -263,7 +264,7 @@ def parse_csv_metadata(interface_name: str, headers: list[str]) -> InterfaceInfo
263264

264265
intf_disp_name = intf_or_part_disp_name
265266
if data_index == 0:
266-
assert intf_info.display_name == ""
267+
assert_(intf_info.display_name == "", "display_name should be empty")
267268
intf_info.display_name = intf_disp_name
268269
series_info = TransferSeriesInfo(
269270
data_index,

src/ansys/systemcoupling/core/charts/plot_functions.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@
3434

3535

3636
def create_and_show_plot(spec: PlotSpec, csv_list: list[str]) -> Plotter:
37-
assert len(spec.interfaces) == 1, "Plots currently only support one interface"
38-
assert len(spec.interfaces) == len(csv_list)
37+
if len(spec.interfaces) != 1:
38+
raise ValueError("Plots currently only support one interface")
39+
if len(spec.interfaces) != len(csv_list):
40+
raise ValueError(
41+
"'csv_list' should have length equal to the number of interfaces"
42+
)
3943

4044
manager = PlotDefinitionManager(spec)
4145
reader = CsvChartDataReader(spec.interfaces[0].name, csv_list[0])
@@ -61,8 +65,12 @@ def solve_with_live_plot(
6165
csv_list: list[str],
6266
solve_func: Callable[[], None],
6367
):
64-
assert len(spec.interfaces) == 1, "Plots currently only support one interface"
65-
assert len(spec.interfaces) == len(csv_list)
68+
if len(spec.interfaces) != 1:
69+
raise ValueError("Plots currently only support one interface")
70+
if len(spec.interfaces) != len(csv_list):
71+
raise ValueError(
72+
"'csv_list' should have length equal to the number of interfaces"
73+
)
6674

6775
manager = PlotDefinitionManager(spec)
6876
dispatcher = MessageDispatcher()

src/ansys/systemcoupling/core/charts/plotter.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from ansys.systemcoupling.core.charts.plotdefinition_manager import (
3838
PlotDefinitionManager,
3939
)
40+
from ansys.systemcoupling.core.util.assertion import assert_
4041

4142

4243
def _process_timestep_data(
@@ -282,13 +283,13 @@ def show_animated(self):
282283
# this (assume the wait function is stored as an
283284
# attribute):
284285
#
285-
# assert self._wait_for_metadata is not None
286+
# assert_(self._wait_for_metadata is not None)
286287
# metadata = self._wait_for_metadata()
287288
# if metadata is not None:
288289
# self.set_metadata(metadata)
289290
# else:
290291
# return
291-
assert self._request_update is not None
292+
assert_(self._request_update is not None)
292293

293294
self.ani = FuncAnimation(
294295
self._fig,

src/ansys/systemcoupling/core/client/grpc_client.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def _reset(self):
101101
self.__output_thread = None
102102
self.__pim_instance = None
103103
self.__skip_exit = False
104+
self.__container = None
104105

105106
@classmethod
106107
def _cleanup(cls):
@@ -161,7 +162,9 @@ def start_container_and_connect(
161162
"""Start the System Coupling container and establish a connection."""
162163
LOG.debug("Starting container...")
163164
port = port if port is not None else _find_port()
164-
start_container(mounted_from, mounted_to, network, port, version)
165+
self.__container = start_container(
166+
mounted_from, mounted_to, network, port, version
167+
)
165168
LOG.debug("...started")
166169
self._connect(_LOCALHOST_IP, port)
167170

@@ -307,12 +310,18 @@ def exit(self):
307310
try:
308311
self.__ostream_service.end_streaming()
309312
except Exception as e:
310-
LOG.debug("Exception on OutputStreamService.end_straming(): " + str(e))
313+
LOG.debug(f"Exception on OutputStreamService.end_straming(): {e}")
311314
self.__process_service.quit()
312315
self.__channel = None
313316
if self.__process:
314317
self.__process.end()
315318
self.__process = None
319+
if self.__container:
320+
try:
321+
self.__container.stop()
322+
except Exception as e:
323+
LOG.debug(f"Exception from container.stop(): {e}")
324+
self.__container = None
316325
if self.__pim_instance is not None:
317326
self.__pim_instance.delete()
318327
self.__pim_instance = None

src/ansys/systemcoupling/core/client/syc_container.py

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222

2323
import os
2424
from pathlib import Path
25-
import subprocess
2625

2726
from ansys.systemcoupling.core.syc_version import SYC_VERSION_DOT, normalize_version
27+
from ansys.systemcoupling.core.util.logging import LOG
2828

2929
_MPI_VERSION_VAR = "FLUENT_INTEL_MPI_VERSION"
3030
_MPI_VERSION = "2021"
@@ -41,15 +41,22 @@ def _image_tag(version: str) -> str:
4141

4242
def start_container(
4343
mounted_from: str, mounted_to: str, network: str, port: int, version: str
44-
) -> None:
44+
) -> object:
4545
"""Start a System Coupling container.
4646
4747
Parameters
4848
----------
4949
port : int
5050
gPRC server local port, mapped to the same port in container.
51+
52+
Returns
53+
-------
54+
object
55+
The container instance (``Container`` object from Python docker library).
5156
"""
52-
args = ["-m", "cosimgui", f"--grpcport=0.0.0.0:{port}"]
57+
import docker
58+
59+
LOG.debug("Starting System Coupling docker container...")
5360

5461
if version:
5562
image_tag = _image_tag(version)
@@ -58,47 +65,45 @@ def start_container(
5865

5966
mounted_from = str(Path(mounted_from).absolute())
6067

61-
run_args = [
62-
"docker",
63-
"run",
64-
"-d",
65-
"--rm",
66-
"-p",
67-
f"{port}:{port}",
68-
"-v",
69-
f"{mounted_from}:{mounted_to}",
70-
"-w",
71-
mounted_to,
72-
"-e",
73-
f"{_MPI_VERSION_VAR}={_MPI_VERSION}",
74-
"-e",
75-
f"AWP_ROOT=/ansys_inc",
76-
f"ghcr.io/ansys/pysystem-coupling:{image_tag}",
77-
] + args
68+
image_name = f"ghcr.io/ansys/pysystem-coupling:{image_tag}"
69+
70+
environment = [f"{_MPI_VERSION_VAR}={_MPI_VERSION}", f"AWP_ROOT=/ansys_inc"]
7871

72+
# Additional environment
7973
container_user = os.getenv("SYC_CONTAINER_USER")
8074
if container_user:
81-
idx = run_args.index("-p")
82-
run_args.insert(idx, container_user)
83-
run_args.insert(idx, "--user")
8475
# Licensing can't log to default location if user is not the default 'root'
85-
run_args.insert(idx, f"ANSYSLC_APPLOGDIR={mounted_to}")
86-
run_args.insert(idx, "-e")
76+
environment.append(f"ANSYSLC_APPLOGDIR={mounted_to}")
77+
# See also "user" argument added to args below
8778

8879
license_server = os.getenv("ANSYSLMD_LICENSE_FILE")
8980
if license_server:
90-
idx = run_args.index("-e")
91-
run_args.insert(idx, f"ANSYSLMD_LICENSE_FILE={license_server}")
92-
run_args.insert(idx, "-e")
93-
81+
environment.append(f"ANSYSLMD_LICENSE_FILE={license_server}")
82+
83+
run_args = dict(
84+
image=image_name,
85+
command=["-m", "cosimgui", f"--grpcport=0.0.0.0:{port}"],
86+
detach=True,
87+
environment=environment,
88+
remove=True,
89+
ports={f"{port}/tcp": f"{port}"},
90+
volumes=[f"{mounted_from}:{mounted_to}"],
91+
working_dir=f"{mounted_to}",
92+
)
93+
94+
# Additional args
95+
if container_user:
96+
run_args["user"] = container_user
9497
if network:
95-
idx = run_args.index("-p")
96-
run_args.insert(idx, network)
97-
run_args.insert(idx, "--network")
98+
run_args["network"] = network
9899

99-
subprocess.run(run_args)
100-
return port
100+
docker_client = docker.from_env()
101+
return docker_client.containers.run(**run_args)
101102

102103

103104
def create_network(name):
104-
subprocess.run(["docker", "network", "create", name])
105+
import docker
106+
107+
LOG.debug(f"Creating docker network '{name}'")
108+
docker_client = docker.from_env()
109+
docker_client.networks.create(name)

src/ansys/systemcoupling/core/client/syc_process.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
from copy import deepcopy
2424
import os
2525
import platform
26-
import subprocess
26+
27+
# Exclude Bandit check. Subprocess is needed to start the System Coupling.
28+
import subprocess # nosec B404
2729

2830
import psutil
2931

@@ -99,7 +101,8 @@ def _start_system_coupling(
99101
args += extra_args
100102

101103
LOG.info(f"Starting System Coupling: {args[0]}")
102-
return subprocess.Popen(
104+
# Exclude Bandit check. No untrusted input to arguments.
105+
return subprocess.Popen( # nosec B603
103106
args,
104107
env=env,
105108
cwd=working_dir,

0 commit comments

Comments
 (0)