|
3 | 3 |
|
4 | 4 | # list schema versions in ascending order |
5 | 5 | SCHEMA_VERSION_KEY = 'version' |
6 | | -SCHEMA_VERSIONS = ['v1', 'v2', 'v3', 'v4'] |
| 6 | +SCHEMA_VERSIONS = ['v1', 'v2', 'v3', 'v4', 'v5'] |
7 | 7 | # use ... (Ellipsis) here to indicate that no default value exists (will lead to error if no value is given) |
8 | 8 | ACCOUNT_DEFAULTS = {'subject': ..., 'api_key': ..., 'roles': ..., 'group': None} |
9 | 9 | PUBLIC_KEY_DEFAULTS = {'public_key': ..., 'public_key_type': ..., 'public_key_name': ...} |
@@ -143,6 +143,23 @@ def db_ensure_schema_v4(cur: cursor): |
143 | 143 | ''') |
144 | 144 |
|
145 | 145 |
|
| 146 | +def db_ensure_schema_v5(cur: cursor): |
| 147 | + # v5 mainly extends v4 |
| 148 | + db_ensure_schema_v4(cur) |
| 149 | + # introduce tables compliance that track compliance over time |
| 150 | + cur.execute(''' |
| 151 | + CREATE TABLE IF NOT EXISTS compliance ( |
| 152 | + resultid SERIAL PRIMARY KEY, |
| 153 | + checked_at timestamp NOT NULL, |
| 154 | + subject text NOT NULL, |
| 155 | + scopeuuid text NOT NULL, |
| 156 | + version text NOT NULL, |
| 157 | + result int, |
| 158 | + approval boolean |
| 159 | + ); |
| 160 | + ''') |
| 161 | + |
| 162 | + |
146 | 163 | def db_upgrade_data_v1_v2(cur): |
147 | 164 | # we are going to drop table result, but use delete anyway to have the transaction safety |
148 | 165 | cur.execute(''' |
@@ -219,6 +236,10 @@ def db_upgrade_schema(conn: connection, cur: cursor): |
219 | 236 | db_ensure_schema_v4(cur) |
220 | 237 | db_set_schema_version(cur, 'v4') |
221 | 238 | conn.commit() |
| 239 | + elif current == 'v4': |
| 240 | + db_ensure_schema_v5(cur) |
| 241 | + db_set_schema_version(cur, 'v5') |
| 242 | + conn.commit() |
222 | 243 |
|
223 | 244 |
|
224 | 245 | def db_ensure_schema(conn: connection): |
@@ -424,3 +445,39 @@ def db_patch_approval2(cur: cursor, record): |
424 | 445 | RETURNING resultid;''', record) |
425 | 446 | resultid, = cur.fetchone() |
426 | 447 | return resultid |
| 448 | + |
| 449 | + |
| 450 | +def db_insert_compliance_result( |
| 451 | + cur: cursor, checked_at, subject, scopeuuid, version, result, approval |
| 452 | +): |
| 453 | + # this is an exception in that we don't use a record parameter (it's just not as practical here) |
| 454 | + cur.execute(''' |
| 455 | + INSERT INTO compliance (checked_at, subject, scopeuuid, version, result, approval) |
| 456 | + VALUES (%s, %s, %s, %s, %s, %s) |
| 457 | + RETURNING resultid;''', (checked_at, subject, scopeuuid, version, result, approval)) |
| 458 | + resultid, = cur.fetchone() |
| 459 | + return resultid |
| 460 | + |
| 461 | + |
| 462 | +def db_get_relevant_compliance_results( |
| 463 | + cur: cursor, |
| 464 | + subject=None, scopeuuid=None, version=None, approved_only=True, |
| 465 | +): |
| 466 | + """for each combination of scope/version/check, get the most recent test result that is still valid""" |
| 467 | + # find the latest result per subject/scopeuuid/version/checkid for this subject |
| 468 | + # DISTINCT ON is a Postgres-specific construct that comes in very handy here :) |
| 469 | + cur.execute(sql.SQL(''' |
| 470 | + SELECT DISTINCT ON (subject, scopeuuid, version) |
| 471 | + subject, scopeuuid, version, result, approval, checked_at |
| 472 | + FROM compliance |
| 473 | + {filter_condition} |
| 474 | + ORDER BY subject, scopeuuid, version, checked_at DESC; |
| 475 | + ''').format( |
| 476 | + filter_condition=make_where_clause( |
| 477 | + sql.SQL('approval') if approved_only else None, |
| 478 | + None if scopeuuid is None else sql.SQL('scopeuuid = %(scopeuuid)s'), |
| 479 | + None if version is None else sql.SQL('version = %(version)s'), |
| 480 | + None if subject is None else sql.SQL('subject = %(subject)s'), |
| 481 | + ), |
| 482 | + ), {"subject": subject, "scopeuuid": scopeuuid, "version": version}) |
| 483 | + return cur.fetchall() |
0 commit comments