-
Notifications
You must be signed in to change notification settings - Fork 952
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Mathusan Selvarajah <[email protected]>
- Loading branch information
Showing
3 changed files
with
324 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
import { Client } from "../apiv2"; | ||
import { developerConnectOrigin, developerConnectP4SAOrigin } from "../api"; | ||
|
||
const PAGE_SIZE_MAX = 100; | ||
|
||
export const client = new Client({ | ||
urlPrefix: developerConnectOrigin, | ||
auth: true, | ||
apiVersion: "v1", | ||
}); | ||
|
||
export interface OperationMetadata { | ||
createTime: string; | ||
endTime: string; | ||
target: string; | ||
verb: string; | ||
requestedCancellation: boolean; | ||
apiVersion: string; | ||
} | ||
|
||
export interface Operation { | ||
name: string; | ||
metadata?: OperationMetadata; | ||
done: boolean; | ||
error?: { code: number; message: string; details: unknown }; | ||
response?: any; | ||
} | ||
|
||
export interface OAuthCredential { | ||
oauthTokenSecretVersion: string; | ||
username: string; | ||
} | ||
|
||
type GitHubApp = "GIT_HUB_APP_UNSPECIFIED" | "DEVELOPER_CONNECT" | "FIREBASE"; | ||
|
||
export interface GitHubConfig { | ||
githubApp?: GitHubApp; | ||
authorizerCredential?: OAuthCredential; | ||
appInstallationId?: string; | ||
installationUri?: string; | ||
} | ||
|
||
type InstallationStage = | ||
| "STAGE_UNSPECIFIED" | ||
| "PENDING_CREATE_APP" | ||
| "PENDING_USER_OAUTH" | ||
| "PENDING_INSTALL_APP" | ||
| "COMPLETE"; | ||
|
||
export interface InstallationState { | ||
stage: InstallationStage; | ||
message: string; | ||
actionUri: string; | ||
} | ||
|
||
export interface Connection { | ||
name: string; | ||
createTime?: string; | ||
updateTime?: string; | ||
deleteTime?: string; | ||
labels?: { | ||
[key: string]: string; | ||
}; | ||
githubConfig?: GitHubConfig; | ||
installationState: InstallationState; | ||
disabled?: boolean; | ||
reconciling?: boolean; | ||
annotations?: { | ||
[key: string]: string; | ||
}; | ||
etag?: string; | ||
uid?: string; | ||
} | ||
|
||
type ConnectionOutputOnlyFields = | ||
| "createTime" | ||
| "updateTime" | ||
| "deleteTime" | ||
| "installationState" | ||
| "reconciling" | ||
| "uid"; | ||
|
||
export interface GitRepositoryLink { | ||
name: string; | ||
cloneUri: string; | ||
createTime: string; | ||
updateTime: string; | ||
deleteTime: string; | ||
labels?: { | ||
[key: string]: string; | ||
}; | ||
etag?: string; | ||
reconciling: boolean; | ||
annotations?: { | ||
[key: string]: string; | ||
}; | ||
uid: string; | ||
} | ||
|
||
type GitRepositoryLinkOutputOnlyFields = | ||
| "createTime" | ||
| "updateTime" | ||
| "deleteTime" | ||
| "reconciling" | ||
| "uid"; | ||
|
||
export interface LinkableGitRepositories { | ||
repositories: LinkableGitRepository[]; | ||
nextPageToken: string; | ||
} | ||
|
||
export interface LinkableGitRepository { | ||
cloneUri: string; | ||
} | ||
|
||
/** | ||
* Creates a Developer Connect Connection. | ||
*/ | ||
export async function createConnection( | ||
projectId: string, | ||
location: string, | ||
connectionId: string, | ||
githubConfig: GitHubConfig, | ||
): Promise<Operation> { | ||
const config: GitHubConfig = { | ||
...githubConfig, | ||
githubApp: "FIREBASE", | ||
}; | ||
const res = await client.post< | ||
Omit<Omit<Connection, "name">, ConnectionOutputOnlyFields>, | ||
Operation | ||
>( | ||
`projects/${projectId}/locations/${location}/connections`, | ||
{ | ||
githubConfig: config, | ||
}, | ||
{ queryParams: { connectionId } }, | ||
); | ||
return res.body; | ||
} | ||
|
||
/** | ||
* Gets details of a single Developer Connect Connection. | ||
*/ | ||
export async function getConnection( | ||
projectId: string, | ||
location: string, | ||
connectionId: string, | ||
): Promise<Connection> { | ||
const name = `projects/${projectId}/locations/${location}/connections/${connectionId}`; | ||
const res = await client.get<Connection>(name); | ||
return res.body; | ||
} | ||
|
||
/** | ||
* List Developer Connect Connections | ||
*/ | ||
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; | ||
} | ||
|
||
/** | ||
* Gets a list of repositories that can be added to the provided Connection. | ||
*/ | ||
export async function fetchLinkableGitRepositories( | ||
projectId: string, | ||
location: string, | ||
connectionId: string, | ||
pageToken = "", | ||
pageSize = 1000, | ||
): Promise<LinkableGitRepositories> { | ||
const name = `projects/${projectId}/locations/${location}/connections/${connectionId}:fetchLinkableRepositories`; | ||
const res = await client.get<LinkableGitRepositories>(name, { | ||
queryParams: { | ||
pageSize, | ||
pageToken, | ||
}, | ||
}); | ||
|
||
return res.body; | ||
} | ||
|
||
/** | ||
* Creates a GitRepositoryLink.Upon linking a Git Repository, Developer | ||
* Connect will configure the Git Repository to send webhook events to | ||
* Developer Connect. | ||
*/ | ||
export async function createGitRepositoryLink( | ||
projectId: string, | ||
location: string, | ||
connectionId: string, | ||
gitRepositoryLinkId: string, | ||
cloneUri: string, | ||
): Promise<Operation> { | ||
const res = await client.post< | ||
Omit<GitRepositoryLink, GitRepositoryLinkOutputOnlyFields | "name">, | ||
Operation | ||
>( | ||
`projects/${projectId}/locations/${location}/connections/${connectionId}/gitRepositoryLinks`, | ||
{ cloneUri }, | ||
{ queryParams: { gitRepositoryLinkId } }, | ||
); | ||
return res.body; | ||
} | ||
|
||
/** | ||
* Get details of a single GitRepositoryLink | ||
*/ | ||
export async function getGitRepositoryLink( | ||
projectId: string, | ||
location: string, | ||
connectionId: string, | ||
gitRepositoryLinkId: string, | ||
): Promise<GitRepositoryLink> { | ||
const name = `projects/${projectId}/locations/${location}/connections/${connectionId}/gitRepositoryLinks/${gitRepositoryLinkId}`; | ||
const res = await client.get<GitRepositoryLink>(name); | ||
return res.body; | ||
} | ||
|
||
/** | ||
* Returns email associated with the Developer Connect Service Agent | ||
*/ | ||
export function serviceAgentEmail(projectNumber: string): string { | ||
return `service-${projectNumber}@${developerConnectP4SAOrigin}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { expect } from "chai"; | ||
import * as sinon from "sinon"; | ||
import * as devconnect from "../../gcp/devConnect"; | ||
|
||
describe("developer connect", () => { | ||
let post: sinon.SinonStub; | ||
let get: sinon.SinonStub; | ||
|
||
const projectId = "project"; | ||
const location = "us-central1"; | ||
const connectionId = "apphosting-connection"; | ||
const connectionsRequestPath = `projects/${projectId}/locations/${location}/connections`; | ||
|
||
beforeEach(() => { | ||
post = sinon.stub(devconnect.client, "post"); | ||
get = sinon.stub(devconnect.client, "get"); | ||
}); | ||
|
||
afterEach(() => { | ||
post.restore(); | ||
get.restore(); | ||
}); | ||
|
||
describe("createConnection", () => { | ||
it("ensures githubConfig is FIREBASE", async () => { | ||
post.returns({ body: {} }); | ||
await devconnect.createConnection(projectId, location, connectionId, {}); | ||
|
||
expect(post).to.be.calledWith( | ||
connectionsRequestPath, | ||
{ githubConfig: { githubApp: "FIREBASE" } }, | ||
{ queryParams: { connectionId } }, | ||
); | ||
}); | ||
}); | ||
|
||
describe("listConnections", () => { | ||
it("interates through all pages and returns a single list", async () => { | ||
const firstConnection = { name: "conn1", installationState: { stage: "COMPLETE" } }; | ||
const secondConnection = { name: "conn2", installationState: { stage: "COMPLETE" } }; | ||
const thirdConnection = { name: "conn3", installationState: { stage: "COMPLETE" } }; | ||
|
||
get | ||
.onFirstCall() | ||
.returns({ | ||
body: { | ||
connections: [firstConnection], | ||
nextPageToken: "someToken", | ||
}, | ||
}) | ||
.onSecondCall() | ||
.returns({ | ||
body: { | ||
connections: [secondConnection], | ||
nextPageToken: "someToken2", | ||
}, | ||
}) | ||
.onThirdCall() | ||
.returns({ | ||
body: { | ||
connections: [thirdConnection], | ||
}, | ||
}); | ||
|
||
const conns = await devconnect.listConnections(projectId, location); | ||
expect(get).callCount(3); | ||
expect(conns).to.deep.equal([firstConnection, secondConnection, thirdConnection]); | ||
}); | ||
}); | ||
}); |