forked from ChelseaKR/fare-policy-assistant
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_release_descriptor.py
More file actions
174 lines (153 loc) · 5.71 KB
/
Copy pathbuild_release_descriptor.py
File metadata and controls
174 lines (153 loc) · 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/env python3
"""Build the deterministic release descriptor from the exact current inputs."""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
from collections.abc import Mapping, Sequence
from pathlib import Path
import yaml
from assistant import config
from assistant.corpus import corpus_version
from assistant.ingest import load_chunks
from assistant.release_identity import (
ReleaseDescriptor,
ReleaseIdentityError,
build_config_identity,
build_release_descriptor,
resolve_current_snapshot,
write_release_descriptor,
)
_EFFECTIVE_ENVIRONMENT_JSON = "FPA_RELEASE_EFFECTIVE_ENVIRONMENT_JSON"
def _git(repo_root: Path, *arguments: str) -> str:
try:
result = subprocess.run(
["git", "-C", str(repo_root), *arguments],
check=True,
capture_output=True,
text=True,
)
except (OSError, subprocess.CalledProcessError) as exc:
raise ReleaseIdentityError("could not inspect the Git source state") from exc
return result.stdout.strip()
def clean_source_revision(repo_root: Path) -> str:
"""Return HEAD only when the complete source checkout is clean."""
revision = _git(repo_root, "rev-parse", "HEAD")
dirty = _git(repo_root, "status", "--porcelain", "--untracked-files=normal")
if dirty:
raise ReleaseIdentityError(
"working tree is dirty; commit the complete release before building a descriptor"
)
return revision
def build_current_descriptor(
source_revision: str,
*,
environment: Mapping[str, str] | None = None,
chunks_path: Path | None = None,
manifest_path: Path | None = None,
raw_dir: Path | None = None,
snapshots_dir: Path | None = None,
prompts_dir: Path | None = None,
answer_schema_path: Path | None = None,
) -> ReleaseDescriptor:
"""Pure, injectable descriptor build after an external clean-source check."""
selected_chunks = chunks_path or config.CHUNKS_PATH
chunks = load_chunks(selected_chunks)
identity = resolve_current_snapshot(
chunks_path=selected_chunks,
manifest_path=manifest_path,
raw_dir=raw_dir,
snapshots_dir=snapshots_dir,
)
config_identity = build_config_identity(
environment,
prompts_dir=prompts_dir,
answer_schema_path=answer_schema_path,
)
return build_release_descriptor(
source_revision,
config_identity,
content_version=identity.content_version,
snapshot_version=identity.snapshot_version,
corpus_version=corpus_version(chunks),
)
def effective_runtime_environment() -> Mapping[str, str]:
"""Read the deployer's final customer environment without printing secrets."""
encoded = os.environ.get(_EFFECTIVE_ENVIRONMENT_JSON)
if encoded is None:
return os.environ
try:
decoded = json.loads(encoded)
except json.JSONDecodeError as exc:
raise ReleaseIdentityError(
f"{_EFFECTIVE_ENVIRONMENT_JSON} must contain valid JSON"
) from exc
if isinstance(decoded, Mapping) and set(decoded) == {"Variables"}:
decoded = decoded["Variables"]
if not isinstance(decoded, Mapping) or any(
not isinstance(key, str) or not isinstance(value, str) for key, value in decoded.items()
):
raise ReleaseIdentityError(
f"{_EFFECTIVE_ENVIRONMENT_JSON} must contain a string environment mapping"
)
values = dict(decoded)
values["AWS_REGION"] = os.environ.get("AWS_REGION", config.DEFAULT_AWS_REGION)
return values
def _secret_free_environment_summary(
descriptor: ReleaseDescriptor,
output: Path,
) -> dict[str, str]:
signing = descriptor.config.payload["runtime"]
assert isinstance(signing, Mapping)
signing = signing["history_signing"]
assert isinstance(signing, Mapping)
key_id = signing["key_id"]
return {
"descriptor_path": str(output),
"FPA_SOURCE_REVISION": descriptor.source_revision,
"FPA_CONFIG_VERSION": descriptor.config_version,
"FPA_PINNED_CONTENT_VERSION": descriptor.content_version,
"FPA_PINNED_SNAPSHOT_VERSION": descriptor.snapshot_version,
"FPA_RELEASE_VERSION": descriptor.release_version,
"FPA_PINNED_CORPUS_VERSION": descriptor.corpus_version,
"FPA_HISTORY_HMAC_KEY_ID": key_id if isinstance(key_id, str) else "",
}
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--output",
type=Path,
default=config.RELEASE_DESCRIPTOR_PATH,
help="canonical descriptor output path",
)
parser.add_argument(
"--source-revision",
help="must equal clean HEAD when supplied (primarily for explicit CI wiring)",
)
return parser
def main(argv: Sequence[str] | None = None) -> int:
args = _parser().parse_args(argv)
try:
revision = clean_source_revision(config.REPO_ROOT)
if args.source_revision is not None and args.source_revision != revision:
raise ReleaseIdentityError("--source-revision does not equal clean Git HEAD")
descriptor = build_current_descriptor(
revision,
environment=effective_runtime_environment(),
)
output = write_release_descriptor(descriptor, args.output)
except (OSError, UnicodeError, ValueError, yaml.YAMLError) as exc:
print(f"release descriptor build failed: {exc}", file=sys.stderr)
return 2
print(
json.dumps(
_secret_free_environment_summary(descriptor, output),
sort_keys=True,
separators=(",", ":"),
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())