Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/guides/portal-execution-profiles-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ have permission to create pipelines in that GitLab project. The Portal records
the request payload, GitLab response metadata, status, and errors in
`execution_requests`; it must not record the token value.

For multiple destinations, configure named targets instead of the single-repo
fallback:

```text
RESULT_SERVER_GITLAB_TARGETS=swc=gitlab.swc.example.org/group/project,gitlab_com=gitlab.com/group/project
RESULT_SERVER_GITLAB_TOKEN_SWC=<site-local GitLab API token>
RESULT_SERVER_GITLAB_TOKEN_GITLAB_COM=<site-local GitLab API token>
```

Target IDs may contain letters, digits, `_`, `.`, and `-`. The token variable is
`RESULT_SERVER_GITLAB_TOKEN_<TARGET_ID>` with non-alphanumeric characters
converted to `_` and uppercased. Store these variables in the Portal systemd
`EnvironmentFile`, not in the repository.

## Compatibility Expectations

Keep the existing `list.csv` and `queue.csv` paths working. Execution profiles
Expand Down
33 changes: 28 additions & 5 deletions result_server/routes/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
)
from utils.gitlab_pipeline import (
build_pipeline_plan,
configured_gitlab_repo,
configured_gitlab_target,
configured_gitlab_targets,
configured_gitlab_token,
submit_pipeline_plan,
)
Expand Down Expand Up @@ -136,6 +137,7 @@ def _build_execution_pipeline_plan(store):
"""Resolve the submitted target and build a GitLab pipeline plan."""
target_ref = request.form.get("target_ref", "").strip() or "develop"
profile_id = request.form.get("profile_id", "").strip()
gitlab_target_id = request.form.get("gitlab_target", "").strip()
code = request.form.get("code", "").strip()
system = request.form.get("system", "").strip()
exp = request.form.get("exp", "").strip()
Expand All @@ -151,8 +153,9 @@ def _build_execution_pipeline_plan(store):
exp=exp,
)
profile = resolve_result.profile
gitlab_target, target_errors = configured_gitlab_target(gitlab_target_id)
plan = build_pipeline_plan(
gitlab_repo=configured_gitlab_repo(),
gitlab_repo=gitlab_target.repo if gitlab_target else "",
target_ref=target_ref,
code=code,
system=system,
Expand All @@ -161,16 +164,19 @@ def _build_execution_pipeline_plan(store):
park_only=park_only,
park_send=park_send,
scheduler_extra_args=resolve_result.scheduler_extra_args,
target_id=gitlab_target.id if gitlab_target else gitlab_target_id,
)
return {
"target_ref": target_ref,
"profile_id": profile_id,
"gitlab_target": gitlab_target,
"gitlab_target_id": gitlab_target.id if gitlab_target else gitlab_target_id,
"code": code,
"system": system,
"exp": exp,
"profile": profile,
"plan": plan,
"errors": resolve_result.errors + plan.errors,
"errors": target_errors + resolve_result.errors + plan.errors,
}


Expand Down Expand Up @@ -226,6 +232,8 @@ def execution_profiles():
"admin_execution_profiles.html",
profile_result=profile_result,
dry_run_result=None,
submit_result=None,
gitlab_targets=configured_gitlab_targets()[0],
)


Expand Down Expand Up @@ -293,6 +301,7 @@ def dry_run_execution_profile_submit():
submit_plan = _build_execution_pipeline_plan(store)
target_ref = submit_plan["target_ref"]
profile_id = submit_plan["profile_id"]
gitlab_target_id = submit_plan["gitlab_target_id"]
code = submit_plan["code"]
system = submit_plan["system"]
exp = submit_plan["exp"]
Expand All @@ -309,7 +318,11 @@ def dry_run_execution_profile_submit():
code=code,
system=system,
exp=exp,
payload={"api_url": plan.api_url, "payload": plan.payload},
payload={
"api_url": plan.api_url,
"gitlab_target": gitlab_target_id,
"payload": plan.payload,
},
errors=errors,
actor=session.get("user_email", ""),
)
Expand All @@ -322,6 +335,7 @@ def dry_run_execution_profile_submit():
details={
"request_id": request_id,
"target_ref": target_ref,
"gitlab_target": gitlab_target_id,
"code": code,
"system": system,
"exp": exp,
Expand All @@ -338,11 +352,13 @@ def dry_run_execution_profile_submit():
"status": status,
"profile": profile,
"api_url": plan.api_url,
"gitlab_target": gitlab_target_id,
"payload_json": json.dumps(plan.payload, indent=2, sort_keys=True),
"errors": errors,
"warnings": plan.warnings,
},
submit_result=None,
gitlab_targets=configured_gitlab_targets()[0],
)


Expand All @@ -356,6 +372,8 @@ def submit_execution_profile_pipeline():
submit_plan = _build_execution_pipeline_plan(store)
target_ref = submit_plan["target_ref"]
profile_id = submit_plan["profile_id"]
gitlab_target = submit_plan["gitlab_target"]
gitlab_target_id = submit_plan["gitlab_target_id"]
code = submit_plan["code"]
system = submit_plan["system"]
exp = submit_plan["exp"]
Expand All @@ -370,7 +388,7 @@ def submit_execution_profile_pipeline():
if not errors:
submit_result = submit_pipeline_plan(
plan,
token=configured_gitlab_token(),
token=configured_gitlab_token(gitlab_target),
)
errors.extend(submit_result.errors)

Expand All @@ -381,6 +399,8 @@ def submit_execution_profile_pipeline():
else:
status = "submit_blocked"
payload = {"api_url": plan.api_url, "payload": plan.payload}
if gitlab_target_id:
payload["gitlab_target"] = gitlab_target_id
if submit_result is not None:
payload["submit"] = {
"status_code": submit_result.status_code,
Expand Down Expand Up @@ -408,6 +428,7 @@ def submit_execution_profile_pipeline():
details={
"request_id": request_id,
"target_ref": target_ref,
"gitlab_target": gitlab_target_id,
"code": code,
"system": system,
"exp": exp,
Expand All @@ -427,12 +448,14 @@ def submit_execution_profile_pipeline():
"status": status,
"profile": profile,
"api_url": plan.api_url,
"gitlab_target": gitlab_target_id,
"payload_json": json.dumps(plan.payload, indent=2, sort_keys=True),
"errors": errors,
"warnings": plan.warnings,
"response": submit_result.response if submit_result else {},
"status_code": submit_result.status_code if submit_result else 0,
},
gitlab_targets=configured_gitlab_targets()[0],
)


Expand Down
30 changes: 30 additions & 0 deletions result_server/templates/admin_execution_profiles.html
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ <h2 class="section-title">GitLab Pipeline Submit</h2>
Target Ref
<input type="text" name="target_ref" value="develop" required>
</label>
<label>
GitLab Target
{% if gitlab_targets %}
<select name="gitlab_target">
{% for target in gitlab_targets %}
<option value="{{ target.id }}">{{ target.id }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" name="gitlab_target" placeholder="default">
{% endif %}
</label>
<label>
Profile ID
<input type="text" name="profile_id" placeholder="optional explicit profile">
Expand Down Expand Up @@ -301,6 +313,9 @@ <h2 class="section-title">GitLab Pipeline Submit</h2>
<div class="inline-notice">
<strong>Dry-run request #{{ dry_run_result.request_id }}:</strong>
{{ dry_run_result.status }}
{% if dry_run_result.gitlab_target %}
target <span class="profile-mono">{{ dry_run_result.gitlab_target }}</span>
{% endif %}
{% if dry_run_result.profile %}
using profile <span class="profile-mono">{{ dry_run_result.profile.id }}</span>
{% endif %}
Expand Down Expand Up @@ -335,6 +350,18 @@ <h2 class="section-title">GitLab Pipeline Submit</h2>
Target Ref
<input type="text" name="target_ref" value="develop" required>
</label>
<label>
GitLab Target
{% if gitlab_targets %}
<select name="gitlab_target">
{% for target in gitlab_targets %}
<option value="{{ target.id }}">{{ target.id }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" name="gitlab_target" placeholder="default">
{% endif %}
</label>
<label>
Profile ID
<input type="text" name="profile_id" placeholder="optional explicit profile">
Expand Down Expand Up @@ -383,6 +410,9 @@ <h2 class="section-title">GitLab Pipeline Submit</h2>
{% if submit_result.status_code %}
HTTP {{ submit_result.status_code }}
{% endif %}
{% if submit_result.gitlab_target %}
target <span class="profile-mono">{{ submit_result.gitlab_target }}</span>
{% endif %}
{% if submit_result.profile %}
using profile <span class="profile-mono">{{ submit_result.profile.id }}</span>
{% endif %}
Expand Down
82 changes: 82 additions & 0 deletions result_server/tests/test_execution_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
from utils.gitlab_pipeline import ( # noqa: E402
GitLabPipelineSubmitResult,
build_pipeline_plan,
configured_gitlab_target,
configured_gitlab_targets,
configured_gitlab_token,
submit_pipeline_plan,
)

Expand Down Expand Up @@ -494,6 +497,27 @@ def test_gitlab_pipeline_submit_blocks_without_token():
assert result.errors == ["RESULT_SERVER_GITLAB_TOKEN is not set"]


def test_gitlab_pipeline_targets_parse_multiple_destinations():
env = {
"RESULT_SERVER_GITLAB_TARGETS": (
"swc=gitlab.swc.example.org/fugakunext/benchmark/benchkit,"
"gitlab_com=gitlab.com/yoshifuminakamura/benchkit"
),
"RESULT_SERVER_GITLAB_TOKEN_SWC": "swc-token",
"RESULT_SERVER_GITLAB_TOKEN_GITLAB_COM": "com-token",
}

targets, errors = configured_gitlab_targets(env)
selected, selected_errors = configured_gitlab_target("gitlab_com", env)

assert errors == []
assert [target.id for target in targets] == ["swc", "gitlab_com"]
assert targets[0].token_env == "RESULT_SERVER_GITLAB_TOKEN_SWC"
assert selected_errors == []
assert selected.repo == "gitlab.com/yoshifuminakamura/benchkit"
assert configured_gitlab_token(selected, env) == "com-token"


def test_admin_execution_profiles_submit_posts_pipeline_and_records_request(
tmp_path,
monkeypatch,
Expand Down Expand Up @@ -553,6 +577,64 @@ def fake_submit(plan, *, token):
_cleanup(temp_dirs)


def test_admin_execution_profiles_submit_uses_selected_gitlab_target(
tmp_path,
monkeypatch,
):
monkeypatch.delenv("RESULT_SERVER_GITLAB_REPO", raising=False)
monkeypatch.delenv("RESULT_SERVER_GITLAB_TOKEN", raising=False)
monkeypatch.setenv(
"RESULT_SERVER_GITLAB_TARGETS",
"swc=gitlab.swc.example.org/fugakunext/benchmark/benchkit,"
"gitlab_com=gitlab.com/yoshifuminakamura/benchkit",
)
monkeypatch.setenv("RESULT_SERVER_GITLAB_TOKEN_GITLAB_COM", "com-token")
db_path = tmp_path / "cx_portal.sqlite3"
ExecutionProfileStore(str(db_path)).upsert_profile(_profile(), actor="admin")
app, temp_dirs = _admin_app(db_path)

def fake_submit(plan, *, token):
assert token == "com-token"
assert plan.target_id == "gitlab_com"
assert plan.api_url == "https://gitlab.com/api/v4/projects/yoshifuminakamura%2Fbenchkit/pipeline"
return GitLabPipelineSubmitResult(
status_code=201,
response={"id": 456, "web_url": "https://gitlab.com/p/456"},
errors=[],
)

monkeypatch.setattr("routes.admin.submit_pipeline_plan", fake_submit)
try:
with app.test_client() as client:
_login_admin(client)
resp = client.post(
"/admin/execution-profiles/submit",
data={
"gitlab_target": "gitlab_com",
"target_ref": "develop",
"code": "qws",
"system": "RIKYU",
"exp": "case0",
"confirm_submit": "on",
},
)

html = resp.data.decode()
assert resp.status_code == 200
assert "submitted" in html
assert "gitlab_com" in html

with sqlite3.connect(db_path) as conn:
row = conn.execute(
"SELECT payload_json FROM execution_requests"
).fetchone()
payload_record = json.loads(row[0])
assert payload_record["gitlab_target"] == "gitlab_com"
assert payload_record["submit"]["response"]["id"] == 456
finally:
_cleanup(temp_dirs)


def test_admin_execution_profiles_submit_requires_confirmation(tmp_path, monkeypatch):
monkeypatch.setenv("RESULT_SERVER_GITLAB_REPO", "gitlab.example.org/group/benchkit.git")
monkeypatch.setenv("RESULT_SERVER_GITLAB_TOKEN", "secret-token")
Expand Down
Loading
Loading