Skip to content

Commit

Permalink
Add utility function to list GCB connections w/ well-known names.
Browse files Browse the repository at this point in the history
  • Loading branch information
taeold committed Nov 27, 2023
1 parent 721c330 commit 6cec615
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 38 deletions.
39 changes: 35 additions & 4 deletions src/gcp/cloudbuild.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Client } from "../apiv2";
import { cloudbuildOrigin } from "../api";

const PAGE_SIZE_MAX = 100;

const client = new Client({
urlPrefix: cloudbuildOrigin,
auth: true,
Expand Down Expand Up @@ -42,7 +44,7 @@ type InstallationStage =
type ConnectionOutputOnlyFields = "createTime" | "updateTime" | "installationState" | "reconciling";

export interface Connection {
name?: string;
name: string;
disabled?: boolean;
annotations?: {
[key: string]: string;
Expand All @@ -62,7 +64,7 @@ export interface Connection {
type RepositoryOutputOnlyFields = "createTime" | "updateTime";

export interface Repository {
name?: string;
name: string;
remoteUri: string;
annotations?: {
[key: string]: string;
Expand All @@ -85,7 +87,10 @@ export async function createConnection(
location: string,
connectionId: string
): Promise<Operation> {
const res = await client.post<Omit<Connection, ConnectionOutputOnlyFields>, Operation>(
const res = await client.post<
Omit<Omit<Connection, "name">, ConnectionOutputOnlyFields>,
Operation
>(
`projects/${projectId}/locations/${location}/connections`,
{ githubConfig: {} },
{ queryParams: { connectionId } }
Expand All @@ -106,6 +111,32 @@ export async function getConnection(
return res.body;
}

/**
* List metadata for a Cloud Build V2 Connection.
*/
export async function listConnections(projectId: string, location: string): Promise<Connection[]> {
const conns: Connection[] = [];
const getNextPage = async (pageToken = ""): Promise<void> => {
const res = await client.get<{
connections: Connection[];
nextPageToken?: string;
}>(`/projects/${projectId}/locations/${location}/connections`, {
queryParams: {
pageSize: PAGE_SIZE_MAX,
pageToken,
},
});
if (Array.isArray(res.body.connections)) {
conns.push(...res.body.connections);
}
if (res.body.nextPageToken) {
await getNextPage(res.body.nextPageToken);
}
};
await getNextPage();
return conns;
}

/**
* Deletes a Cloud Build V2 Connection.
*/
Expand Down Expand Up @@ -142,7 +173,7 @@ export async function createRepository(
repositoryId: string,
remoteUri: string
): Promise<Operation> {
const res = await client.post<Omit<Repository, RepositoryOutputOnlyFields>, Operation>(
const res = await client.post<Omit<Repository, RepositoryOutputOnlyFields | "name">, Operation>(
`projects/${projectId}/locations/${location}/connections/${connectionId}/repositories`,
{ remoteUri },
{ queryParams: { repositoryId } }
Expand Down
7 changes: 7 additions & 0 deletions src/init/features/frameworks/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import * as utils from "../../../utils";
import { promptOnce } from "../../../prompt";
import * as clc from "colorette";

const FRAMEWORKS_CONN_PATTERN = /.+\/frameworks-github-conn-.+$/;

const gcbPollerOptions: Omit<poller.OperationPollerOptions, "operationResourceName"> = {
apiOrigin: cloudbuildOrigin,
apiVersion: "v2",
Expand Down Expand Up @@ -188,3 +190,8 @@ export async function getOrCreateRepository(
}
return repo;
}

export async function listFrameworksConnections(projectId: string) {

Check warning on line 194 in src/init/features/frameworks/repo.ts

View workflow job for this annotation

GitHub Actions / lint (18)

Missing return type on function

Check warning on line 194 in src/init/features/frameworks/repo.ts

View workflow job for this annotation

GitHub Actions / lint (18)

Missing JSDoc comment
const conns = await gcb.listConnections(projectId, "-");
return conns.filter((conn) => FRAMEWORKS_CONN_PATTERN.test(conn.name));
}
127 changes: 93 additions & 34 deletions src/test/init/frameworks/repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,51 @@ import { expect } from "chai";
import * as gcb from "../../../gcp/cloudbuild";
import * as prompt from "../../../prompt";
import * as poller from "../../../operation-poller";
import { FirebaseError } from "../../../error";
import * as repo from "../../../init/features/frameworks/repo";
import * as utils from "../../../utils";
import { Connection } from "../../../gcp/cloudbuild";
import { FirebaseError } from "../../../error";

describe("composer", () => {
const sandbox: sinon.SinonSandbox = sinon.createSandbox();

let promptOnceStub: sinon.SinonStub;
let pollOperationStub: sinon.SinonStub;
let getConnectionStub: sinon.SinonStub;
let getRepositoryStub: sinon.SinonStub;
let createConnectionStub: sinon.SinonStub;
let createRepositoryStub: sinon.SinonStub;
let fetchLinkableRepositoriesStub: sinon.SinonStub;

beforeEach(() => {
promptOnceStub = sandbox.stub(prompt, "promptOnce").throws("Unexpected promptOnce call");
pollOperationStub = sandbox
.stub(poller, "pollOperation")
.throws("Unexpected pollOperation call");
getConnectionStub = sandbox.stub(gcb, "getConnection").throws("Unexpected getConnection call");
getRepositoryStub = sandbox.stub(gcb, "getRepository").throws("Unexpected getRepository call");
createConnectionStub = sandbox
.stub(gcb, "createConnection")
.throws("Unexpected createConnection call");
createRepositoryStub = sandbox
.stub(gcb, "createRepository")
.throws("Unexpected createRepository call");
fetchLinkableRepositoriesStub = sandbox
.stub(gcb, "fetchLinkableRepositories")
.throws("Unexpected fetchLinkableRepositories call");

sandbox.stub(utils, "openInBrowser").resolves();
});
describe("connect GitHub repo", () => {
const sandbox: sinon.SinonSandbox = sinon.createSandbox();

afterEach(() => {
sandbox.verifyAndRestore();
});
let promptOnceStub: sinon.SinonStub;
let pollOperationStub: sinon.SinonStub;
let getConnectionStub: sinon.SinonStub;
let getRepositoryStub: sinon.SinonStub;
let createConnectionStub: sinon.SinonStub;
let createRepositoryStub: sinon.SinonStub;
let fetchLinkableRepositoriesStub: sinon.SinonStub;

beforeEach(() => {
promptOnceStub = sandbox.stub(prompt, "promptOnce").throws("Unexpected promptOnce call");
pollOperationStub = sandbox
.stub(poller, "pollOperation")
.throws("Unexpected pollOperation call");
getConnectionStub = sandbox
.stub(gcb, "getConnection")
.throws("Unexpected getConnection call");
getRepositoryStub = sandbox
.stub(gcb, "getRepository")
.throws("Unexpected getRepository call");
createConnectionStub = sandbox
.stub(gcb, "createConnection")
.throws("Unexpected createConnection call");
createRepositoryStub = sandbox
.stub(gcb, "createRepository")
.throws("Unexpected createRepository call");
fetchLinkableRepositoriesStub = sandbox
.stub(gcb, "fetchLinkableRepositories")
.throws("Unexpected fetchLinkableRepositories call");

sandbox.stub(utils, "openInBrowser").resolves();
});

afterEach(() => {
sandbox.verifyAndRestore();
});

describe("connect GitHub repo", () => {
const projectId = "projectId";
const location = "us-central1";
const connectionId = `frameworks-${location}`;
Expand Down Expand Up @@ -130,4 +135,58 @@ describe("composer", () => {
await expect(repo.linkGitHubRepository(projectId, location)).to.be.rejected;
});
});

describe("listFrameworksConnections", () => {
const sandbox: sinon.SinonSandbox = sinon.createSandbox();
let listConnectionsStub: sinon.SinonStub;

const projectId = "projectId";
const location = "us-central1";

function mockConn(id: string): Connection {
return {
name: `projects/${projectId}/locations/${location}/connections/${id}`,
disabled: false,
createTime: "0",
updateTime: "1",
installationState: {
stage: "COMPLETE",
message: "complete",
actionUri: "https://google.com",
},
reconciling: false,
};
}

function extractId(name: string): string {
const parts = name.split("/");
return parts.pop() ?? "";
}

beforeEach(() => {
listConnectionsStub = sandbox
.stub(gcb, "listConnections")
.throws("Unexpected getConnection call");
});

afterEach(() => {
sandbox.verifyAndRestore();
});

it("filters out non-frameworks connections", async () => {
listConnectionsStub.resolves([
mockConn("frameworks-github-conn-baddcafe"),
mockConn("hooray-conn"),
mockConn("frameworks-github-conn-deadbeef"),
mockConn("frameworks-github-oauth"),
]);

const conns = await repo.listFrameworksConnections(projectId);
expect(conns).to.have.length(2);
expect(conns.map((c) => extractId(c.name))).to.include.members([
"frameworks-github-conn-baddcafe",
"frameworks-github-conn-deadbeef",
]);
});
});
});

0 comments on commit 6cec615

Please sign in to comment.