diff --git a/sso-mfa/bootstrap/reconcile-keycape-client-secret.py b/sso-mfa/bootstrap/reconcile-keycape-client-secret.py index 209dac1..a0074b8 100755 --- a/sso-mfa/bootstrap/reconcile-keycape-client-secret.py +++ b/sso-mfa/bootstrap/reconcile-keycape-client-secret.py @@ -74,9 +74,12 @@ def main(): report['custody_matches_live'] = bcrypt.checkpw(custody_value(root), hashed) print(json.dumps(report, sort_keys=True)) return 0 if report['custody_matches_live'] or not apply else 1 - except (Exception, KeyboardInterrupt): + except (Exception, KeyboardInterrupt) as error: + # Class and exit code only: messages and kubectl stderr may carry values. + detail = {'error': type(error).__name__} + if isinstance(error, subprocess.CalledProcessError): detail['exit_code'] = error.returncode print(json.dumps({'result': 'unverified', 'phase': phase, - 'partial_changes_possible': phase in ('write', 'readback')}), file=sys.stderr) + 'partial_changes_possible': phase in ('write', 'readback'), **detail}), file=sys.stderr) return 1 diff --git a/tests/test_reconcile_keycape_client_secret.py b/tests/test_reconcile_keycape_client_secret.py index e701d56..f1d20a4 100644 --- a/tests/test_reconcile_keycape_client_secret.py +++ b/tests/test_reconcile_keycape_client_secret.py @@ -83,7 +83,8 @@ def test_refuses_when_live_pair_disagrees(case): before = env.read_bytes() result = run(case, '--apply') assert result.returncode == 1 and result.stdout == '' - assert json.loads(result.stderr) == {'result': 'unverified', 'phase': 'live-pair', 'partial_changes_possible': False} + assert json.loads(result.stderr) == {'result': 'unverified', 'phase': 'live-pair', 'partial_changes_possible': False, + 'error': 'Refused'} assert env.read_bytes() == before @@ -93,3 +94,12 @@ def test_missing_custody_copy_is_reported_as_custody(case): result = run(case) assert result.returncode == 1 assert json.loads(result.stderr)['phase'] == 'custody' + + +def test_cluster_failure_reports_exit_code_only(case): + *_, environ, _ = case + (Path(environ['STATE']) / 'keycape-config').unlink() + result = run(case) + report = json.loads(result.stderr) + assert report['phase'] == 'cluster' and report['error'] == 'CalledProcessError' and report['exit_code'] == 1 + assert LIVE not in result.stderr