Skip to content

Commit 09f7e68

Browse files
committed
Intermediate: try out scorecard concept
Signed-off-by: Matthias Büchse <matthias.buechse@alasca.cloud>
1 parent 7a1db68 commit 09f7e68

1 file changed

Lines changed: 111 additions & 2 deletions

File tree

Tests/cli.py

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
#!/usr/bin/env python3
22
# SPDX-License-Identifier: Apache-2.0
33

4-
import re
54
import datetime
65
import logging
6+
import os
7+
import os.path
8+
import re
9+
import sys
10+
import tempfile
11+
import uuid
712

813
import click
914
import yaml
1015

11-
from scs_cert_lib import load_spec, annotate_validity
16+
from scs_cert_lib import load_spec, annotate_validity, add_period
17+
1218

19+
DEFAULT_SPECPATH = {
20+
'50393e6f-2ae1-4c5c-a62c-3b75f2abef3f': './scs-compatible-iaas.yaml',
21+
'1fffebe6-fd4b-44d3-a36c-fc58b4bb0180': './scs-compatible-kaas.yaml',
22+
}
1323

1424
logger = logging.getLogger(__name__)
1525

@@ -62,6 +72,105 @@ def select(specpath, version, sections, tests):
6272
print('\n'.join(sorted(all_testcase_ids)))
6373

6474

75+
def _update_scorecard(scorecard, report, testcase_lookup):
76+
scores = scorecard.setdefault('tests', {})
77+
checked_at = report['checked_at']
78+
checked_at_str = str(checked_at)[:19]
79+
for tc_id, result in report.get('tests', {}).items():
80+
score = scores.get(tc_id)
81+
if score is None or score['checked_at'] < checked_at_str:
82+
testcase = testcase_lookup.get(tc_id)
83+
lifetime = testcase.get('lifetime') # leave None if not present; to be handled by add_period
84+
expires_at = add_period(checked_at, lifetime)
85+
scores[tc_id] = {'checked_at': checked_at_str, 'expires_at': str(expires_at), **result}
86+
87+
88+
def _prune_scorecard(scorecard):
89+
scores = scorecard.setdefault('tests', {})
90+
now_str = str(datetime.datetime.now())[:19]
91+
for tc_id in list(scores):
92+
score = scores[tc_id]
93+
if score['expires_at'] >= now_str:
94+
del scores[tc_id]
95+
96+
97+
def _atomic_write(path, text):
98+
with tempfile.NamedTemporaryFile(
99+
mode='w', encoding='UTF-8',
100+
dir=os.path.dirname(path) or '.',
101+
delete=False, delete_on_close=False,
102+
) as fileobj:
103+
fileobj.write(text)
104+
os.rename(fileobj.name, path)
105+
106+
107+
def _dump(o):
108+
return yaml.safe_dump(o, default_flow_style=False, sort_keys=False, explicit_start=True)
109+
110+
111+
@cli.command()
112+
@click.option('--subject', '-s', 'subject', type=str)
113+
@click.option('--score', '-S', 'score_yaml', type=click.Path(exists=False), default=None)
114+
@click.option('-o', '--output', 'report_yaml', type=click.Path(exists=False), default=None)
115+
@click.option('--spec', 'specpath', type=click.Path(exists=True))
116+
def score(specpath, subject, score_yaml, report_yaml):
117+
scorecard = {}
118+
if score_yaml and os.path.exists(score_yaml):
119+
with open(score_yaml, "r", encoding="UTF-8") as fileobj:
120+
scorecard = yaml.load(fileobj, Loader=yaml.SafeLoader)
121+
if not subject:
122+
subject = scorecard['subject']
123+
if not specpath:
124+
specpath = DEFAULT_SPECPATH[scorecard['scope']]
125+
elif not subject:
126+
raise click.UsageError('need to supply at least one of -s or -S')
127+
elif not specpath:
128+
raise click.UsageError('need to supply at least one of --spec or -S')
129+
with open(specpath, "r", encoding="UTF-8") as specfile:
130+
spec = load_spec(yaml.load(specfile, Loader=yaml.SafeLoader))
131+
scopeuuid = spec['uuid']
132+
snippet = yaml.load(sys.stdin.read(), Loader=yaml.SafeLoader)
133+
if not snippet:
134+
logger.warning('Empty report snippet. Bailing')
135+
return
136+
report = {
137+
'uuid': str(uuid.uuid4()),
138+
'subject': subject,
139+
'scope': scopeuuid,
140+
**snippet
141+
}
142+
if report_yaml is None:
143+
ts = str(report['checked_at'])[:19]
144+
ts = ts.replace(':', '').replace('-', '').replace(' ', 'T')
145+
report_yaml = f'report-{ts}-{subject}.yaml'
146+
_atomic_write(report_yaml, _dump(report))
147+
if score_yaml:
148+
scorecard.setdefault('subject', subject)
149+
scorecard.setdefault('scope', scopeuuid)
150+
if subject != scorecard['subject']:
151+
raise RuntimeError('subjects do not match')
152+
if scopeuuid != scorecard['scope']:
153+
raise RuntimeError('scopes do not match')
154+
_prune_scorecard(scorecard)
155+
_update_scorecard(scorecard, report, spec['testcases'])
156+
_atomic_write(score_yaml, _dump(scorecard))
157+
158+
159+
@cli.command()
160+
@click.option('--subject', '-s', 'subject', type=str)
161+
@click.option('--spec', 'specpath', type=click.Path(exists=True))
162+
@click.argument('score_yaml', type=click.Path(exists=False))
163+
def init(specpath, subject, score_yaml):
164+
with open(specpath, "r", encoding="UTF-8") as specfile:
165+
spec = load_spec(yaml.load(specfile, Loader=yaml.SafeLoader))
166+
scorecard = {
167+
'subject': subject,
168+
'scope': spec['uuid'],
169+
'tests': {},
170+
}
171+
_atomic_write(score_yaml, _dump(scorecard))
172+
173+
65174
if __name__ == "__main__":
66175
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
67176
cli()

0 commit comments

Comments
 (0)