Skip to content

Commit 1f79554

Browse files
committed
Initial environment detection
1 parent 31aaf7f commit 1f79554

2 files changed

Lines changed: 202 additions & 0 deletions

File tree

src/hermes_plugin_software_card/curate.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
)
1818
from software_card_policies.report import create_report
1919

20+
from hermes_plugin_software_card import environment
21+
2022

2123
class SoftwareCaRDCuratePlugin(BaseCuratePlugin):
2224
"""Software CaRD curation plugin."""
@@ -30,6 +32,8 @@ def __init__(self, command, ctx):
3032
self._validation_graph = None
3133
self._report = None
3234

35+
self._environment = environment.get()
36+
3337
def prepare(self):
3438
"""Prepare the validation.
3539
@@ -49,6 +53,13 @@ def validate(self):
4953
def create_report(self):
5054
"""Create basic text report."""
5155
self._report = create_report(self._validation_graph)
56+
if self._environment is None:
57+
print("Software CaRD plugin not running in CI environment.")
58+
else:
59+
print(
60+
"Find the Software CaRD user interface at:",
61+
environment.format_app_url("https://example.com", environment),
62+
)
5263

5364
def is_publication_approved(self) -> bool:
5465
"""Decide whether the publication of the software is approved."""
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# SPDX-FileCopyrightText: 2026 Helmholtz-Zentrum Dresden - Rossendorf e.V. (HZDR)
2+
# SPDX-FileContributor: David Pape
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
"""Classes to get information about the execution environment."""
7+
8+
import os
9+
from dataclasses import dataclass, fields
10+
from datetime import datetime
11+
from typing import Self
12+
from urllib.parse import urlencode
13+
14+
15+
@dataclass(kw_only=True)
16+
class Environment:
17+
"""Base class for representing computing environments."""
18+
19+
@classmethod
20+
def from_env(cls) -> Self | None:
21+
"""Create object from environment variables.
22+
23+
If not running in GitHub Actions, ``None`` is returned instead.
24+
"""
25+
env = dict(os.environ)
26+
data = {}
27+
for field in fields(cls):
28+
key = field.name.casefold()
29+
value = env.get(field.name)
30+
type_ = field.type
31+
32+
if type_ is str:
33+
pass
34+
if type_ is int:
35+
value = int(value)
36+
if type_ is bool:
37+
value = value.casefold() not in ["false", "f", "0", "none", "null", ""]
38+
if type_ is datetime:
39+
value = datetime.fromisoformat(value)
40+
41+
data[key] = value
42+
43+
return cls(**data)
44+
45+
def url_data(self) -> dict[str, str]:
46+
"""Return the data to be passed to the Software CaRD user interface."""
47+
return {}
48+
49+
50+
class GitLabCIEnvironment(Environment):
51+
"""Environment variables in the GitLab CI environment.
52+
53+
This class exposes only a selection of the environment variables; more may be added.
54+
An overview of all variables available in GitLab CI can be found at:
55+
https://docs.gitlab.com/ci/variables/predefined_variables/
56+
"""
57+
58+
ci_commit_author: str # author line, e.g. "Jane Doe <j.doe@example.com>"
59+
ci_commit_branch: str # e.g. "main"
60+
ci_commit_title: str # first line without newline
61+
ci_commit_message: str # first line with newline
62+
ci_commit_description: str # the following lines
63+
ci_commit_message_is_truncated: bool
64+
ci_commit_ref_name: str # e.g. "main"
65+
ci_commit_ref_protected: bool
66+
ci_commit_sha: str # the full SHA of the commit
67+
ci_commit_short_sha: str # the shortened sha of the commit (8 characters)
68+
ci_commit_timestamp: datetime
69+
70+
ci_default_branch: str # e.g. "main"
71+
72+
ci_job_group_name: str
73+
ci_job_id: int
74+
ci_job_name: str
75+
ci_job_stage: str
76+
ci_job_started_at: datetime
77+
ci_job_url: str # e.g. "https://codebase.helmholtz.cloud/my-group/my-project/-/jobs/1234567"
78+
ci_pipeline_created_at: datetime
79+
ci_pipeline_id: int
80+
ci_pipeline_iid: int
81+
ci_pipeline_name: str
82+
ci_pipeline_source: str # push/...
83+
# e.g. "https://codebase.helmholtz.cloud/my-group/my-project/-/pipelines/123456"
84+
ci_pipeline_url: str
85+
86+
ci_project_id: int
87+
ci_project_name: str # e.g. "my-project"
88+
ci_project_path: str # e.g. "my-group/my-project"
89+
ci_project_title: str # e.g. "My Project"
90+
# e.g. "https://codebase.helmholtz.cloud/my-group/my-project"
91+
ci_project_url: str
92+
93+
ci_server_name: str # e.g. "GitLab"
94+
ci_server_url: str # e.g. "https://codebase.helmholtz.cloud"
95+
ci_server_version: str # e.g. "18.7.2"
96+
97+
gitlab_user_email: str # full email address
98+
gitlab_user_id: int
99+
gitlab_user_login: str # username
100+
gitlab_user_name: str # display name
101+
102+
@classmethod
103+
def from_env(cls) -> Self | None:
104+
"""Create object from environment variables.
105+
106+
If not running in GitLab CI, ``None`` is returned instead.
107+
"""
108+
env = dict(os.environ)
109+
if env.get("CI") != "true" or env.get("GITLAB_CI") != "true":
110+
return None
111+
112+
return super().from_env()
113+
114+
def url_data(self):
115+
"""Return the data to be passed to the Software CaRD user interface."""
116+
return {
117+
"gitlab_ci_server": self.ci_server_url,
118+
"gitlab_ci_job": self.ci_job_url,
119+
}
120+
121+
122+
class GitHubActionsEnvironment(Environment):
123+
"""Environment variables in the GitHub Actions environment.
124+
125+
This class exposes only a selection of the environment variables; more may be added.
126+
An overview of all variables available in GitHub Actions can be found at:
127+
https://docs.github.com/en/actions/reference/workflows-and-actions/variables
128+
"""
129+
130+
github_event_name: str # push/pull_request/schedule/...
131+
132+
github_actor: str # the username
133+
github_actor_id: int
134+
github_triggering_actor: str # the username
135+
136+
github_workflow: str # Specified with `name:` in workflow config
137+
github_job: str # key of the sections under `jobs:`
138+
github_run_attempt: int
139+
github_run_id: int
140+
github_run_number: int
141+
142+
github_repository: str # "<group or username>/<repo>"
143+
github_repository_id: int
144+
github_repository_owner: str # group or username
145+
github_repository_owner_id: int
146+
147+
github_sha: str # the full SHA of the commit
148+
github_ref: str # e.g. "refs/heads/main"
149+
github_ref_name: str # e.g. "main"
150+
github_ref_protected: bool
151+
github_ref_type: str # branch/tag/...
152+
153+
github_api_url: str # https://api.github.com
154+
github_server_url: str # https://github.com
155+
156+
@classmethod
157+
def from_env(cls) -> Self | None:
158+
"""Create object from environment variables.
159+
160+
If not running in GitHub Actions, ``None`` is returned instead.
161+
"""
162+
env = dict(os.environ)
163+
if env.get("CI") != "true" or env.get("GITHUB_ACTIONS") != "true":
164+
return None
165+
166+
return super().from_env()
167+
168+
def url_data(self):
169+
"""Return the data to be passed to the Software CaRD user interface."""
170+
return {
171+
"github_ci_server": self.github_server_url,
172+
"github_ci_job": self.github_run_id,
173+
}
174+
175+
176+
def get() -> Environment | None:
177+
"""Return the CI environment that we are running in, or ``None``."""
178+
github_actions = GitHubActionsEnvironment.from_env()
179+
gitlab_ci = GitLabCIEnvironment.from_env()
180+
181+
if github_actions is not None and gitlab_ci is not None:
182+
message = "More than one CI environment detected"
183+
raise RuntimeError(message)
184+
185+
return github_actions or gitlab_ci
186+
187+
188+
def format_app_url(base_url: str, environment: Environment) -> str:
189+
"""Format the url for visiting the Software CaRD web interface."""
190+
query = urlencode(environment.url_data())
191+
return f"{base_url}?{query}"

0 commit comments

Comments
 (0)