const GCP_OAUTH_CLIENT_ID = '946220775492-a5v6bsdin6o7ncnqn34snuatmrp7dqh0.apps.googleusercontent.com' ; const GCP_OAUTH_CLIENT_SECRET = 'lQT4Qx9b3CaSHDcnuYFgyYVE' ; export class GcpError extends Error { constructor ( code , message ? ) { super ( `Error ${ code }: ${ message }` ) ; Object . setPrototypeOf ( this , new . target . prototype ) ; this . name = new . target . name ; } } see https export type Instance = Readonly < { id : string ; creationTimestamp : string ; name : string ; description : string ; tags : { items : string [ ] ; fingerprint : string ; } ; machineType : string ; zone : string ; networkInterfaces : Array < { network : string ; subnetwork : string ; networkIP : string ; ipv6Address : string ; name : string ; accessConfigs : Array < { type : string ; name : string ; natIP : string ; setPublicPtr : boolean ; publicPtrDomainName : string ; networkTier : string ; kind : string ; } > ; } > ; } > ; see https type StaticIp = Readonly < { } > ; const GCE_V1_API = 'https://compute.googleapis.com/compute/v1' ; function projectUrl ( projectId ) { return `${ GCE_V1_API }/projects/${ projectId }` ; } export interface RegionLocator { /** The GCP project ID. */ projectId : string ; /** The region of the operation. */ regionId : string ; } function regionUrl ( { projectId , regionId } ) { return `${ projectUrl ( projectId ) }/regions/${ regionId }` ; } /**
 * Represents the scope of a zonal operation
 */ export interface ZoneLocator { /** The GCP project ID. */ projectId : string ; /** The zone of the operation. */ zoneId : string ; } function zoneUrl ( { projectId , zoneId } ) { return `${ projectUrl ( projectId ) }/zones/${ zoneId }` ; } const zoneUrlRegExp = new RegExp ( '/compute/v1/projects/(?<projectId>[^/]+)/zones/(?<zoneId>[^/]+)$' ) ; export function parseZoneUrl ( url ) { const groups = new URL ( url ) . pathname . match ( zoneUrlRegExp ) . groups ; return { projectId : groups [ 'projectId' ] , zoneId : groups [ 'zoneId' ] } ; } /**
 * Helper type to avoid error-prone positional arguments to instance-related
 * functions.
 */ export interface InstanceLocator extends ZoneLocator { /** The ID of the instance. */ instanceId : string ; } function instanceUrl ( instance ) { return `${ zoneUrl ( instance ) }/instances/${ instance . instanceId }` ; } see https type GuestAttributes = Readonly < { variableKey : string ; variableValue : string ; queryPath : string ; queryValue : { items : Array < { namespace : string ; key : string ; value : string ; } > ; } ; } > ; see https type Zone = Readonly < { id : string ; creationTimestamp : string ; name : string ; description : string ; status : 'UP' | 'DOWN' ; region : string ; } > ; type Status = Readonly < { code : number ; message : string ; } > ; see https export type ResourceManagerOperation = Readonly < { name : string ; done : boolean ; error : Status ; } > ; see https see https export type ComputeEngineOperation = Readonly < { id : string ; name : string ; targetId : string ; status : string ; error : { errors : Status [ ] ; } ; } > ; see https type ServiceUsageOperation = Readonly < { name : string ; done : boolean ; error : Status ; } > ; see https export type Project = Readonly < { projectNumber : string ; projectId : string ; name : string ; lifecycleState : string ; } > ; see https type Firewall = Readonly < { id : string ; name : string ; } > ; /** https://cloud.google.com/billing/docs/reference/rest/v1/billingAccounts */ export type BillingAccount = Readonly < { name : string ; open : boolean ; displayName : string ; masterBillingAccount : string ; } > ; /** https://cloud.google.com/billing/docs/reference/rest/v1/ProjectBillingInfo */ export type ProjectBillingInfo = Readonly < { name : string ; projectId : string ; billingAccountName ? : string ; billingEnabled ? : boolean ; } > ; see https export type UserInfo = Readonly < { email : string ; } > ; type Service = Readonly < { name : string ; config : { name : string ; } ; state : 'STATE_UNSPECIFIED' | 'DISABLED' | 'ENABLED' ; } > ; type ItemsResponse < T > = Readonly < { items : T ; nextPageToken : string ; } > ; type ListInstancesResponse = ItemsResponse < Instance [ ] > ; type ListAllInstancesResponse = ItemsResponse < { [ zone ] : { instances : Instance [ ] ; } ; } > ; type ListZonesResponse = ItemsResponse < Zone [ ] > ; type ListProjectsResponse = Readonly < { projects : Project [ ] ; nextPageToken : string ; } > ; type ListFirewallsResponse = ItemsResponse < Firewall [ ] > ; type ListBillingAccountsResponse = Readonly < { billingAccounts : BillingAccount [ ] ; nextPageToken : string ; } > ; type ListEnabledServicesResponse = Readonly < { services : Service [ ] ; nextPageToken : string ; } > ; type RefreshAccessTokenResponse = Readonly < { access_token : string ; expires_in : number ; } > ; export class HttpError extends Error { constructor ( private statusCode , message ? ) { super ( message ) ; } getStatusCode ( ) { return this . statusCode ; } } export class RestApiClient { private readonly GCP_HEADERS = new Map < string , string > ( [ [ 'Content-type' , 'application/json' ] , [ 'Accept' , 'application/json' ] , ] ) ; private accessToken : string ; constructor ( private refreshToken ) { } see https param zone param data return async createInstance ( zone , data ) { return this . fetchAuthenticated < ComputeEngineOperation > ( 'POST' , new URL ( `${ zoneUrl ( zone ) }/instances` ) , this . GCP_HEADERS , null , data ) ; } see https param instance return deleteInstance ( instance ) { return this . fetchAuthenticated < ComputeEngineOperation > ( 'DELETE' , new URL ( instanceUrl ( instance ) ) , this . GCP_HEADERS ) ; } see https param instance getInstance ( instance ) { return this . fetchAuthenticated ( 'GET' , new URL ( instanceUrl ( instance ) ) , this . GCP_HEADERS ) ; } see https param zone param filter listInstances ( zone , filter ? ) { let parameters = null ; if ( filter ) { parameters = new Map < string , string > ( [ [ 'filter' , filter ] , ] ) ; } return this . fetchAuthenticated ( 'GET' , new URL ( `${ zoneUrl ( zone ) }/instances` ) , this . GCP_HEADERS , parameters ) ; } see https param projectId param filter listAllInstances ( projectId , filter ? ) { let parameters = null ; if ( filter ) { parameters = new Map < string , string > ( [ [ 'filter' , filter ] , ] ) ; } return this . fetchAuthenticated ( 'GET' , new URL ( `${ projectUrl ( projectId ) }/aggregated/instances` ) , this . GCP_HEADERS , parameters ) ; } see https param region param data async createStaticIp ( region , data ) { const operation = await this . fetchAuthenticated < ComputeEngineOperation > ( 'POST' , new URL ( `${ regionUrl ( region ) }/addresses` ) , this . GCP_HEADERS , null , data ) ; return await this . computeEngineOperationRegionWait ( region , operation . name ) ; } see https param region param addressName return deleteStaticIp ( region , addressName ) { return this . fetchAuthenticated < ComputeEngineOperation > ( 'DELETE' , new URL ( `${ regionUrl ( region ) }/addresses/${ addressName }` ) , this . GCP_HEADERS ) ; } see https param region param addressName getStaticIp ( region , addressName ) { return this . fetchAuthenticated < ComputeEngineOperation > ( 'GET' , new URL ( `${ regionUrl ( region ) }/addresses/${ addressName }` ) , this . GCP_HEADERS ) ; } see https see https param instance param namespace async getGuestAttributes ( instance , namespace ) { try { const parameters = new Map < string , string > ( [ [ 'queryPath' , namespace ] ] ) ; return await this . fetchAuthenticated ( 'GET' , new URL ( `${ instanceUrl ( instance ) }/getGuestAttributes` ) , this . GCP_HEADERS , parameters ) ; } catch ( error ) { return undefined ; } } see https param projectId param data async createFirewall ( projectId , data ) { const operation = await this . fetchAuthenticated < ComputeEngineOperation > ( 'POST' , new URL ( `${ projectUrl ( projectId ) }/global/firewalls` ) , this . GCP_HEADERS , null , data ) ; return await this . computeEngineOperationGlobalWait ( projectId , operation . name ) ; } param projectId param name listFirewalls ( projectId , name ) { const filter = `name=${ name }` ; const parameters = new Map < string , string > ( [ [ 'filter' , filter ] ] ) ; return this . fetchAuthenticated ( 'GET' , new URL ( `${ projectUrl ( projectId ) }/global/firewalls` ) , this . GCP_HEADERS , parameters ) ; } see https param projectId listZones ( projectId ) { return this . fetchAuthenticated ( 'GET' , new URL ( `${ projectUrl ( projectId ) }/zones` ) , this . GCP_HEADERS ) ; } param projectId listEnabledServices ( projectId ) { const parameters = new Map < string , string > ( [ [ 'filter' , 'state:ENABLED' ] ] ) ; return this . fetchAuthenticated ( 'GET' , new URL ( `https://serviceusage.googleapis.com/v1/projects/${ projectId }/services` ) , this . GCP_HEADERS , parameters ) ; } param projectId param data enableServices ( projectId , data ) { return this . fetchAuthenticated ( 'POST' , new URL ( `https://serviceusage.googleapis.com/v1/projects/${ projectId }/services:batchEnable` ) , this . GCP_HEADERS , null , data ) ; } see https param data createProject ( data ) { return this . fetchAuthenticated ( 'POST' , new URL ( 'https://cloudresourcemanager.googleapis.com/v1/projects' ) , this . GCP_HEADERS , null , data ) ; } see https param filter listProjects ( filter ? ) { let parameters = null ; if ( filter ) { parameters = new Map < string , string > ( [ [ 'filter' , filter ] , ] ) ; } return this . fetchAuthenticated ( 'GET' , new URL ( 'https://cloudresourcemanager.googleapis.com/v1/projects' ) , this . GCP_HEADERS , parameters ) ; } see https param projectId getProjectBillingInfo ( projectId ) { return this . fetchAuthenticated ( 'GET' , new URL ( `https://cloudbilling.googleapis.com/v1/projects/${ projectId }/billingInfo` ) , this . GCP_HEADERS ) ; } see https param projectId param data updateProjectBillingInfo ( projectId , data ) { return this . fetchAuthenticated ( 'PUT' , new URL ( `https://cloudbilling.googleapis.com/v1/projects/${ projectId }/billingInfo` ) , this . GCP_HEADERS , null , data ) ; } see https listBillingAccounts ( ) { return this . fetchAuthenticated ( 'GET' , new URL ( `https://cloudbilling.googleapis.com/v1/billingAccounts` ) , this . GCP_HEADERS ) ; } see https param zone param operationId async computeEngineOperationZoneWait ( zone , operationId ) { const operation = await this . fetchAuthenticated < ComputeEngineOperation > ( 'POST' , new URL ( `${ zoneUrl ( zone ) }/operations/${ operationId }/wait` ) , this . GCP_HEADERS ) ; if ( operation . error ?. errors ) { throw new GcpError ( operation ?. error . errors [ 0 ] ?. code , operation ?. error . errors [ 0 ] ?. message ) ; } return operation ; } see https param region param operationId computeEngineOperationRegionWait ( region , operationId ) { return this . fetchAuthenticated ( 'POST' , new URL ( `${ regionUrl ( region ) }/operations/${ operationId }/wait` ) , this . GCP_HEADERS ) ; } see https param projectId param operationId computeEngineOperationGlobalWait ( projectId , operationId ) { return this . fetchAuthenticated ( 'POST' , new URL ( `${ projectUrl ( projectId ) }/global/operations/${ operationId }/wait` ) , this . GCP_HEADERS ) ; } resourceManagerOperationGet ( operationId ) { return this . fetchAuthenticated ( 'GET' , new URL ( `https://cloudresourcemanager.googleapis.com/v1/${ operationId }` ) , this . GCP_HEADERS ) ; } serviceUsageOperationGet ( operationId ) { return this . fetchAuthenticated ( 'GET' , new URL ( `https://serviceusage.googleapis.com/v1/${ operationId }` ) , this . GCP_HEADERS ) ; } see https see https see https getUserInfo ( ) { const parameters = new Map < string , string > ( [ [ 'access_token' , this . accessToken ] ] ) ; return this . fetchAuthenticated ( 'POST' , new URL ( 'https://openidconnect.googleapis.com/v1/userinfo' ) , this . GCP_HEADERS ) ; } private async refreshGcpAccessToken ( refreshToken ) { const headers = new Map < string , string > ( [ [ 'Host' , 'oauth2.googleapis.com' ] , [ 'Content-Type' , 'application/x-www-form-urlencoded' ] ] ) ; const data = { client_id : GCP_OAUTH_CLIENT_ID , client_secret : GCP_OAUTH_CLIENT_SECRET , refresh_token : refreshToken , grant_type : 'refresh_token' , } ; const encodedData = this . encodeFormData ( data ) ; const response = await this . fetchUnauthenticated ( 'POST' , new URL ( 'https://oauth2.googleapis.com/token' ) , headers , null , encodedData ) ; return response . access_token ; } see https param token private async revokeGcpToken ( token ) { const headers = new Map < string , string > ( [ [ 'Host' , 'oauth2.googleapis.com' ] , [ 'Content-Type' , 'application/x-www-form-urlencoded' ] ] ) ; const parameters = new Map < string , string > ( [ [ 'token' , token ] ] ) ; return this . fetchUnauthenticated ( 'GET' , new URL ( 'https://oauth2.googleapis.com/revoke' ) , headers , parameters ) ; } private async fetchAuthenticated < T > ( method , url , headers , parameters ? , data ? ) { const httpHeaders = new Map ( headers ) ; if ( ! this . accessToken ) { this . accessToken = await this . refreshGcpAccessToken ( this . refreshToken ) ; } httpHeaders . set ( 'Authorization' , `Bearer ${ this . accessToken }` ) ; return this . fetchUnauthenticated ( method , url , httpHeaders , parameters , data ) ; } private async fetchUnauthenticated < T > ( method , url , headers , parameters ? , data ? ) { const customHeaders = new Headers ( ) ; headers . forEach ( ( value , key ) => { customHeaders . append ( key , value ) ; } ) ; if ( parameters ) { parameters . forEach ( ( value , key ) => url . searchParams . append ( key , value ) ) ; } if ( typeof data === 'object' ) { data = JSON . stringify ( data ) ; } const response = await fetch ( url . toString ( ) , { method : method . toUpperCase ( ) , headers : customHeaders , ... ( data && { body : data } ) , } ) ; if ( ! response . ok ) { throw new HttpError ( response . status , response . statusText ) ; } try { let result = undefined ; if ( response . status !== 204 ) { result = await response . json ( ) ; } return result ; } catch ( e ) { throw new Error ( 'Error parsing response body: ' + JSON . stringify ( e ) ) ; } } private encodeFormData ( data ) { return Object . entries ( data ) . map ( entry => { return encodeURIComponent ( entry [ 0 ] ) + '=' + encodeURIComponent ( entry [ 1 ] ) ; } ) . join ( '&' ) ; } }