Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utility function to list GCB connections w/ well-known names. #6535

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 All @@ -21,7 +23,7 @@
metadata?: OperationMetadata;
done: boolean;
error?: { code: number; message: string; details: unknown };
response?: any;

Check warning on line 26 in src/gcp/cloudbuild.ts

View workflow job for this annotation

GitHub Actions / lint (18)

Unexpected any. Specify a different type
}

export interface GitHubConfig {
Expand All @@ -42,7 +44,7 @@
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 @@
type RepositoryOutputOnlyFields = "createTime" | "updateTime";

export interface Repository {
name?: string;
name: string;
remoteUri: string;
annotations?: {
[key: string]: string;
Expand All @@ -85,7 +87,10 @@
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 @@
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 @@
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 All @@ -167,7 +198,7 @@
/**
* Deletes a Cloud Build V2 Repository.
*/
export async function deleteRepository(

Check warning on line 201 in src/gcp/cloudbuild.ts

View workflow job for this annotation

GitHub Actions / lint (18)

Missing return type on function
projectId: string,
location: string,
connectionId: string,
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 { 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 @@ -94,7 +96,7 @@
value: "",
});

return await promptOnce({

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

View workflow job for this annotation

GitHub Actions / lint (18)

Unsafe return of an `any` typed value
type: "list",
message: "Which of the following repositories would you like to deploy?",
choices,
Expand Down Expand Up @@ -188,3 +190,8 @@
}
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",
]);
});
});
});
Loading