From b9ca6d20ccd4372222303953c34aa67df84fd965 Mon Sep 17 00:00:00 2001
From: Joey Perrott <josephperrott@gmail.com>
Date: Tue, 1 Sep 2020 11:29:32 -0700
Subject: [PATCH] feat(dev-infra): include CI status check in the caretaker
 check (#38779)

Add a CI status check in the ng-dev caretaker check command.

PR Close #38779
---
 dev-infra/caretaker/check/check.ts |  3 ++
 dev-infra/caretaker/check/ci.ts    | 59 ++++++++++++++++++++++++++++++
 2 files changed, 62 insertions(+)
 create mode 100644 dev-infra/caretaker/check/ci.ts

diff --git a/dev-infra/caretaker/check/check.ts b/dev-infra/caretaker/check/check.ts
index 9fdfb8b984..ebd51125f8 100644
--- a/dev-infra/caretaker/check/check.ts
+++ b/dev-infra/caretaker/check/check.ts
@@ -9,6 +9,7 @@
 import {GitClient} from '../../utils/git';
 import {getCaretakerConfig} from '../config';
 
+import {printCiStatus} from './ci';
 import {printG3Comparison} from './g3';
 import {printGithubTasks} from './github';
 import {printServiceStatuses} from './services';
@@ -21,7 +22,9 @@ export async function checkServiceStatuses(githubToken: string) {
   /** The GitClient for interacting with git and Github. */
   const git = new GitClient(githubToken, config);
 
+  // TODO(josephperrott): Allow these checks to be loaded in parallel.
   await printServiceStatuses();
   await printGithubTasks(git, config.caretaker);
   await printG3Comparison(git);
+  await printCiStatus(git);
 }
diff --git a/dev-infra/caretaker/check/ci.ts b/dev-infra/caretaker/check/ci.ts
new file mode 100644
index 0000000000..2e568e14d5
--- /dev/null
+++ b/dev-infra/caretaker/check/ci.ts
@@ -0,0 +1,59 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import fetch from 'node-fetch';
+
+import {bold, green, info, red} from '../../utils/console';
+import {GitClient} from '../../utils/git';
+
+
+/** The results of checking the status of CI.  */
+interface StatusCheckResult {
+  status: 'success'|'failed'|'canceled'|'infrastructure_fail'|'timedout'|'failed'|'no_tests';
+  timestamp: Date;
+  buildUrl: string;
+}
+
+/** Retrieve and log status of CI for the project. */
+export async function printCiStatus(git: GitClient) {
+  info.group(bold(`CI`));
+  // TODO(josephperrott): Expand list of branches checked to all active branches.
+  await printStatus(git, 'master');
+  info.groupEnd();
+  info();
+}
+
+/** Log the status of CI for a given branch to the console. */
+async function printStatus(git: GitClient, branch: string) {
+  const result = await getStatusOfBranch(git, branch);
+  const branchName = branch.padEnd(10);
+  if (result === null) {
+    info(`${branchName} was not found on CircleCI`);
+  } else if (result.status === 'success') {
+    info(`${branchName} ✅`);
+  } else {
+    info(`${branchName} ❌ (Ran at: ${result.timestamp.toLocaleString()})`);
+  }
+}
+
+/** Get the CI status of a given branch from CircleCI. */
+async function getStatusOfBranch(git: GitClient, branch: string): Promise<StatusCheckResult|null> {
+  const {owner, name} = git.remoteConfig;
+  const url = `https://circleci.com/api/v1.1/project/gh/${owner}/${name}/tree/${
+      branch}?limit=1&filter=completed&shallow=true`;
+  const result = (await fetch(url).then(result => result.json()))?.[0];
+
+  if (result) {
+    return {
+      status: result.outcome,
+      timestamp: new Date(result.stop_time),
+      buildUrl: result.build_url
+    };
+  }
+  return null;
+}