verifyActiveGcloudAccount method

Future<bool> verifyActiveGcloudAccount({
  1. required String projectId,
})

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;
}