verifyActiveGcloudAccount method
Preflight: verify the active gcloud account is plausibly authorized
for projectId. Returns true when:
- No account is active (let downstream gcloud calls fail with their own clear errors), OR
- The active account is a user-account / non-SA email (any user can in principle have IAM bindings on any project), OR
- The active account is a service-account for this same project
(
<sa>@<projectId>.iam.gserviceaccount.com).
Returns false when the active account is a service-account from a
different project — this is almost always the result of stale
gcloud config set account … state from a previous oracular run
against another project, and downstream API enable / repo create
calls will fail with PERMISSION_DENIED from a different project's
SA. We surface that as one actionable error here instead of a
cascade of warnings deep in the deploy.
When we can suggest a better candidate (a SA matching projectId or
a non-SA user account already credentialed via gcloud auth list),
we print the exact gcloud config set account … command to copy.
Implementation
Future<bool> verifyActiveGcloudAccount({required String projectId}) async {
final ProcessResult r = await _runner.run('gcloud', <String>[
'config',
'get-value',
'account',
]);
if (!r.success) return true; // gcloud not installed / unauthenticated
final String active = r.stdout.trim();
if (active.isEmpty || active == '(unset)') return true;
final Match? saMatch = _serviceAccountEmailRegex.firstMatch(active);
if (saMatch == null) return true; // user-account → assume OK
final String saProject = saMatch.group(1)!;
if (saProject == projectId) return true; // SA belongs to target project
error('Active gcloud account is `$active`,');
error('but you are deploying to project `$projectId`.');
error('This service account belongs to project `$saProject` and almost');
error('certainly cannot enable APIs / push images for `$projectId`.');
print('');
print('Suggested fix:');
// Try to find a better candidate among the credentialed accounts
// (`gcloud auth list`). Preferred order:
// 1. A service account matching `projectId`
// (`*@<projectId>.iam.gserviceaccount.com`).
// 2. A non-SA user account (gmail / workspace email).
// 3. Generic `gcloud auth login` fallback.
final ProcessResult listResult = await _runner.run('gcloud', <String>[
'auth',
'list',
'--format=value(account)',
]);
if (listResult.success) {
final List<String> accounts = listResult.stdout
.split('\n')
.map((String s) => s.trim())
.where((String s) => s.isNotEmpty)
.toList(growable: false);
String? matchingSa;
String? userAccount;
for (final String acct in accounts) {
final Match? m = _serviceAccountEmailRegex.firstMatch(acct);
if (m != null) {
if (m.group(1) == projectId) {
matchingSa = acct;
break;
}
} else {
userAccount ??= acct;
}
}
if (matchingSa != null) {
print(' gcloud config set account $matchingSa');
} else if (userAccount != null) {
print(' gcloud config set account $userAccount');
} else {
print(' gcloud auth login '
'# log in with an account that has roles/owner on $projectId');
}
print(' oracular deploy all # then re-run');
} else {
print(' gcloud config set account <email>');
print(' oracular deploy all');
}
return false;
}