From 166e9d316aa24a9682e828ce7da1e526c8b07570 Mon Sep 17 00:00:00 2001 From: Paul Balaji Date: Thu, 1 Feb 2024 16:58:12 +0000 Subject: [PATCH] feat: improve create-key logging (#3207) --- .../infra/scripts/announce-validators.ts | 8 ++- typescript/infra/scripts/utils.ts | 19 +++++-- typescript/infra/src/agents/aws/key.ts | 50 +++++++++++++++++-- typescript/infra/src/agents/gcp.ts | 32 +++++++++--- typescript/infra/src/agents/key-utils.ts | 47 ++++++++++++----- typescript/infra/src/utils/gcloud.ts | 25 ++++++++-- 6 files changed, 150 insertions(+), 31 deletions(-) diff --git a/typescript/infra/scripts/announce-validators.ts b/typescript/infra/scripts/announce-validators.ts index 509c0d5237..f070329f3e 100644 --- a/typescript/infra/scripts/announce-validators.ts +++ b/typescript/infra/scripts/announce-validators.ts @@ -126,7 +126,9 @@ async function main() { const announced = announcedLocations[0].includes(location); if (!announced) { const signature = ethers.utils.joinSignature(announcement.signature); - console.log(`Announcing ${address} checkpoints at ${location}`); + console.log( + `[${chain}] Announcing ${address} checkpoints at ${location}`, + ); await validatorAnnounce.announce( address, location, @@ -134,7 +136,9 @@ async function main() { multiProvider.getTransactionOverrides(chain), ); } else { - console.log(`Already announced ${address} checkpoints at ${location}`); + console.log( + `[${chain}] Already announced ${address} checkpoints at ${location}`, + ); } } } diff --git a/typescript/infra/scripts/utils.ts b/typescript/infra/scripts/utils.ts index 645aa2ca05..9447f4d24a 100644 --- a/typescript/infra/scripts/utils.ts +++ b/typescript/infra/scripts/utils.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import path from 'path'; import yargs from 'yargs'; @@ -29,6 +30,8 @@ import { EnvironmentNames, deployEnvToSdkEnv } from '../src/config/environment'; import { Role } from '../src/roles'; import { assertContext, assertRole, readJSON } from '../src/utils/utils'; +const debugLog = debug('infra:scripts:utils'); + export enum Modules { // TODO: change PROXY_FACTORY = 'ism', @@ -130,6 +133,7 @@ export function assertEnvironment(env: string): DeployEnvironment { } export function getEnvironmentConfig(environment: DeployEnvironment) { + debugLog(`Getting environment config for ${environment}`); return environments[environment]; } @@ -154,12 +158,17 @@ export function getAgentConfig( typeof environment == 'string' ? getEnvironmentConfig(environment) : environment; + + debugLog( + `Getting agent config for ${context} context in ${coreConfig.environment} environment`, + ); + const agentConfig = coreConfig.agents[context]; if (!agentConfig) throw Error( - `Invalid context ${context} for environment, must be one of ${Object.keys( - coreConfig.agents, - )}.`, + `Invalid context ${context} for ${ + coreConfig.environment + } environment, must be one of ${Object.keys(coreConfig.agents)}.`, ); return agentConfig; } @@ -171,6 +180,7 @@ export function getKeyForRole( role: Role, index?: number, ): CloudAgentKey { + debugLog(`Getting key for ${role} role`); const environmentConfig = environments[environment]; const agentConfig = getAgentConfig(context, environmentConfig); return getCloudAgentKey(agentConfig, role, chain, index); @@ -185,7 +195,9 @@ export async function getMultiProviderForRole( // TODO: rename to consensusType? connectionType?: RpcConsensusType, ): Promise { + debugLog(`Getting multiprovider for ${role} role`); if (process.env.CI === 'true') { + debugLog('Returning multiprovider with default RPCs in CI'); return new MultiProvider(); // use default RPCs } const multiProvider = new MultiProvider(txConfigs); @@ -212,6 +224,7 @@ export async function getKeysForRole( index?: number, ): Promise> { if (process.env.CI === 'true') { + debugLog('No keys to return in CI'); return {}; } diff --git a/typescript/infra/src/agents/aws/key.ts b/typescript/infra/src/agents/aws/key.ts index fb42d10aab..a6c47369e5 100644 --- a/typescript/infra/src/agents/aws/key.ts +++ b/typescript/infra/src/agents/aws/key.ts @@ -16,6 +16,7 @@ import { UpdateAliasCommand, } from '@aws-sdk/client-kms'; import { KmsEthersSigner } from 'aws-kms-ethers-signer'; +import { Debugger, debug } from 'debug'; import { ethers } from 'ethers'; import { AgentSignerKeyType, ChainName } from '@hyperlane-xyz/sdk'; @@ -41,6 +42,7 @@ export class AgentAwsKey extends CloudAgentKey { private client: KMSClient | undefined; private region: string; public remoteKey: RemoteKey = { fetched: false }; + protected logger: Debugger; constructor( agentConfig: AgentContextConfig, @@ -53,16 +55,22 @@ export class AgentAwsKey extends CloudAgentKey { throw new Error('Not configured as AWS'); } this.region = agentConfig.aws.region; + this.logger = debug(`infra:agents:key:aws:${this.identifier}`); } get privateKey(): string { + this.logger( + 'Attempting to access private key, which is unavailable for AWS keys', + ); throw new Error('Private key unavailable for AWS keys'); } async getClient(): Promise { if (this.client) { + this.logger('Returning existing KMSClient instance'); return this.client; } + this.logger('Creating new KMSClient instance'); this.client = new KMSClient({ region: this.region, }); @@ -94,6 +102,7 @@ export class AgentAwsKey extends CloudAgentKey { } async fetch() { + this.logger('Fetching key'); const address = await this.fetchAddressFromAws(); this.remoteKey = { fetched: true, @@ -102,24 +111,28 @@ export class AgentAwsKey extends CloudAgentKey { } async createIfNotExists() { + this.logger('Checking if key exists and creating if not'); const keyId = await this.getId(); // If it doesn't exist, create it if (!keyId) { - // TODO should this be awaited? create is async - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.create(); + this.logger('Key does not exist, creating new key'); + await this.create(); // It can take a moment for the change to propagate await sleep(1000); + } else { + this.logger('Key already exists'); } await this.fetch(); } async delete() { + this.logger('Delete operation called, but not implemented'); throw Error('Not implemented yet'); } // Allows the `userArn` to use the key async putKeyPolicy(userArn: string) { + this.logger(`Putting key policy for user ARN: ${userArn}`); const client = await this.getClient(); const policy = { Version: '2012-10-17', @@ -151,22 +164,29 @@ export class AgentAwsKey extends CloudAgentKey { PolicyName: 'default', // This is the only accepted name }); await client.send(cmd); + this.logger('Key policy put successfully'); } // Gets the Key's ID if it exists, undefined otherwise async getId() { try { + this.logger('Attempting to describe key to get ID'); const keyDescription = await this.describeKey(); - return keyDescription.KeyMetadata?.KeyId; + const keyId = keyDescription.KeyMetadata?.KeyId; + this.logger(`Key ID retrieved: ${keyId}`); + return keyId; } catch (err: any) { if (err.name === 'NotFoundException') { + this.logger('Key not found'); return undefined; } + this.logger(`Error retrieving key ID: ${err}`); throw err; } } create() { + this.logger('Creating new key'); return this._create(false); } @@ -175,6 +195,7 @@ export class AgentAwsKey extends CloudAgentKey { * @returns The address of the new key */ update() { + this.logger('Updating key (creating new key for rotation)'); return this._create(true); } @@ -182,6 +203,7 @@ export class AgentAwsKey extends CloudAgentKey { * Requires update to have been called on this key prior */ async rotate() { + this.logger('Rotating keys'); const canonicalAlias = this.identifier; const newAlias = canonicalAlias + '-new'; const oldAlias = canonicalAlias + '-old'; @@ -226,15 +248,19 @@ export class AgentAwsKey extends CloudAgentKey { // Address should have changed now await this.fetch(); + this.logger('Keys rotated successfully'); } async getSigner( provider?: ethers.providers.Provider, ): Promise { + this.logger('Getting signer'); const keyId = await this.getId(); if (!keyId) { + this.logger('Key ID not defined, cannot get signer'); throw Error('Key ID not defined'); } + this.logger(`Creating KmsEthersSigner with key ID: ${keyId}`); // @ts-ignore We're using a newer version of Provider than // KmsEthersSigner. The return type for getFeeData for this newer // type is a superset of the return type for getFeeData for the older type, @@ -252,12 +278,15 @@ export class AgentAwsKey extends CloudAgentKey { private requireFetched() { if (!this.remoteKey.fetched) { + this.logger('Key has not been fetched yet'); throw new Error('Key not fetched'); } + this.logger('Key has been fetched'); } // Creates a new key and returns its address private async _create(rotate: boolean) { + this.logger(`Creating key with rotation: ${rotate}`); const client = await this.getClient(); const alias = this.identifier; if (!rotate) { @@ -269,6 +298,7 @@ export class AgentAwsKey extends CloudAgentKey { (_) => _.AliasName === alias, ); if (match) { + this.logger(`Alias ${alias} already exists`); throw new Error( `Attempted to create new key but alias ${alias} already exists`, ); @@ -288,6 +318,7 @@ export class AgentAwsKey extends CloudAgentKey { const createResponse = await client.send(command); if (!createResponse.KeyMetadata) { + this.logger('KeyMetadata was not returned when creating the key'); throw new Error('KeyMetadata was not returned when creating the key'); } const keyId = createResponse.KeyMetadata?.KeyId; @@ -298,10 +329,12 @@ export class AgentAwsKey extends CloudAgentKey { ); const address = this.fetchAddressFromAws(keyId); + this.logger(`New key created with ID: ${keyId}`); return address; } private async fetchAddressFromAws(keyId?: string) { + this.logger(`Fetching address from AWS for key ID: ${keyId}`); const client = await this.getClient(); if (!keyId) { @@ -312,10 +345,15 @@ export class AgentAwsKey extends CloudAgentKey { new GetPublicKeyCommand({ KeyId: keyId }), ); - return getEthereumAddress(Buffer.from(publicKeyResponse.PublicKey!)); + const address = getEthereumAddress( + Buffer.from(publicKeyResponse.PublicKey!), + ); + this.logger(`Address fetched: ${address}`); + return address; } private async describeKey(): Promise { + this.logger('Describing key'); const client = await this.getClient(); return client.send( new DescribeKeyCommand({ @@ -325,6 +363,7 @@ export class AgentAwsKey extends CloudAgentKey { } private async getAliases(): Promise { + this.logger('Getting aliases'); const client = await this.getClient(); let aliases: AliasListEntry[] = []; let marker: string | undefined = undefined; @@ -350,6 +389,7 @@ export class AgentAwsKey extends CloudAgentKey { break; } } + this.logger(`Aliases retrieved: ${aliases.length}`); return aliases; } } diff --git a/typescript/infra/src/agents/gcp.ts b/typescript/infra/src/agents/gcp.ts index 4ba314256e..e0fc75874a 100644 --- a/typescript/infra/src/agents/gcp.ts +++ b/typescript/infra/src/agents/gcp.ts @@ -1,9 +1,6 @@ -import { - encodeSecp256k1Pubkey, - pubkeyToAddress, - rawSecp256k1PubkeyToRawAddress, -} from '@cosmjs/amino'; +import { encodeSecp256k1Pubkey, pubkeyToAddress } from '@cosmjs/amino'; import { Keypair } from '@solana/web3.js'; +import { Debugger, debug } from 'debug'; import { Wallet, ethers } from 'ethers'; import { ChainName } from '@hyperlane-xyz/sdk'; @@ -42,6 +39,8 @@ interface FetchedKey { type RemoteKey = UnfetchedKey | FetchedKey; export class AgentGCPKey extends CloudAgentKey { + protected logger: Debugger; + constructor( environment: DeployEnvironment, context: Contexts, @@ -51,18 +50,23 @@ export class AgentGCPKey extends CloudAgentKey { private remoteKey: RemoteKey = { fetched: false }, ) { super(environment, context, role, chainName, index); + this.logger = debug(`infra:agents:key:gcp:${this.identifier}`); } async createIfNotExists() { + this.logger('Checking if key exists and creating if not'); try { await this.fetch(); + this.logger('Key already exists'); } catch (err) { + this.logger('Key does not exist, creating new key'); await this.create(); } } serializeAsAddress() { this.requireFetched(); + this.logger('Serializing key as address'); return { identifier: this.identifier, // @ts-ignore @@ -98,6 +102,7 @@ export class AgentGCPKey extends CloudAgentKey { addressForProtocol(protocol: ProtocolType): string | undefined { this.requireFetched(); + this.logger(`Getting address for protocol: ${protocol}`); switch (protocol) { case ProtocolType.Ethereum: @@ -106,7 +111,7 @@ export class AgentGCPKey extends CloudAgentKey { return Keypair.fromSeed( Buffer.from(strip0x(this.privateKey), 'hex'), ).publicKey.toBase58(); - case ProtocolType.Cosmos: + case ProtocolType.Cosmos: { const compressedPubkey = ethers.utils.computePublicKey( this.privateKey, true, @@ -117,12 +122,15 @@ export class AgentGCPKey extends CloudAgentKey { // TODO support other prefixes? // https://cosmosdrops.io/en/tools/bech32-converter is useful for converting to other prefixes. return pubkeyToAddress(encodedPubkey, 'neutron'); + } default: + this.logger(`Unsupported protocol: ${protocol}`); return undefined; } } async fetch() { + this.logger('Fetching key'); const secret: SecretManagerPersistedKeys = (await fetchGCPSecret( this.identifier, )) as any; @@ -131,25 +139,34 @@ export class AgentGCPKey extends CloudAgentKey { privateKey: secret.privateKey, address: secret.address, }; + this.logger(`Key fetched successfully: ${secret.address}`); } async create() { + this.logger('Creating new key'); this.remoteKey = await this._create(false); + this.logger('Key created successfully'); } async update() { + this.logger('Updating key'); this.remoteKey = await this._create(true); + this.logger('Key updated successfully'); return this.address; } async delete() { + this.logger('Deleting key'); await execCmd(`gcloud secrets delete ${this.identifier} --quiet`); + this.logger('Key deleted successfully'); } async getSigner( provider?: ethers.providers.Provider, ): Promise { + this.logger('Getting signer'); if (!this.remoteKey.fetched) { + this.logger('Key not fetched, fetching now'); await this.fetch(); } return new Wallet(this.privateKey, provider); @@ -157,12 +174,14 @@ export class AgentGCPKey extends CloudAgentKey { private requireFetched() { if (!this.remoteKey.fetched) { + this.logger('Key not fetched, throwing error'); throw new Error("Can't persist without address"); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars private async _create(rotate: boolean) { + this.logger(`Creating key with rotation: ${rotate}`); const wallet = Wallet.createRandom(); const address = await wallet.getAddress(); const identifier = this.identifier; @@ -187,6 +206,7 @@ export class AgentGCPKey extends CloudAgentKey { }), }, ); + this.logger('Key creation data persisted to GCP'); return { fetched: true, diff --git a/typescript/infra/src/agents/key-utils.ts b/typescript/infra/src/agents/key-utils.ts index 11e669d238..b597cb395e 100644 --- a/typescript/infra/src/agents/key-utils.ts +++ b/typescript/infra/src/agents/key-utils.ts @@ -1,3 +1,5 @@ +import debug from 'debug'; + import { ChainMap, ChainName } from '@hyperlane-xyz/sdk'; import { objMap } from '@hyperlane-xyz/utils'; @@ -17,6 +19,8 @@ import { AgentAwsKey } from './aws/key'; import { AgentGCPKey } from './gcp'; import { CloudAgentKey } from './keys'; +const debugLog = debug('infra:agents:key:utils'); + export interface KeyAsAddress { identifier: string; address: string; @@ -156,6 +160,7 @@ function getRoleKeyMapPerChain( export function getAllCloudAgentKeys( agentConfig: RootAgentConfig, ): Array { + debugLog('Retrieving all cloud agent keys'); const keysPerChain = getRoleKeyMapPerChain(agentConfig); const keysByIdentifier = Object.keys(keysPerChain).reduce( @@ -190,21 +195,22 @@ export function getCloudAgentKey( chainName?: ChainName, index?: number, ): CloudAgentKey { + debugLog(`Retrieving cloud agent key for ${role} on ${chainName}`); switch (role) { case Role.Validator: if (chainName === undefined || index === undefined) { - throw Error(`Must provide chainName and index for validator key`); + throw Error('Must provide chainName and index for validator key'); } // For now just get the validator key, and not the chain signer. return getValidatorKeysForChain(agentConfig, chainName, index).validator; case Role.Relayer: if (chainName === undefined) { - throw Error(`Must provide chainName for relayer key`); + throw Error('Must provide chainName for relayer key'); } return getRelayerKeyForChain(agentConfig, chainName); case Role.Kathy: if (chainName === undefined) { - throw Error(`Must provide chainName for kathy key`); + throw Error('Must provide chainName for kathy key'); } return getKathyKeyForChain(agentConfig, chainName); case Role.Deployer: @@ -223,6 +229,7 @@ export function getRelayerKeyForChain( agentConfig: AgentContextConfig, chainName: ChainName, ): CloudAgentKey { + debugLog(`Retrieving relayer key for ${chainName}`); // If AWS is enabled and the chain is an Ethereum-based chain, we want to use // an AWS key. if (agentConfig.aws && isEthereumProtocolChain(chainName)) { @@ -240,6 +247,7 @@ export function getKathyKeyForChain( agentConfig: AgentContextConfig, chainName: ChainName, ): CloudAgentKey { + debugLog(`Retrieving kathy key for ${chainName}`); // If AWS is enabled and the chain is an Ethereum-based chain, we want to use // an AWS key. if (agentConfig.aws && isEthereumProtocolChain(chainName)) { @@ -252,6 +260,7 @@ export function getKathyKeyForChain( // Returns the deployer key. This is always a GCP key, not chain specific, // and in the Hyperlane context. export function getDeployerKey(agentConfig: AgentContextConfig): CloudAgentKey { + debugLog('Retrieving deployer key'); return new AgentGCPKey(agentConfig.runEnv, Contexts.Hyperlane, Role.Deployer); } @@ -267,6 +276,7 @@ export function getValidatorKeysForChain( validator: CloudAgentKey; chainSigner: CloudAgentKey; } { + debugLog(`Retrieving validator keys for ${chainName}`); const validator = agentConfig.aws ? new AgentAwsKey(agentConfig, Role.Validator, chainName, index) : new AgentGCPKey( @@ -279,15 +289,19 @@ export function getValidatorKeysForChain( // If the chain is Ethereum-based, we can just use the validator key (even if it's AWS-based) // as the chain signer. Otherwise, we need to use a GCP key. - const chainSigner = isEthereumProtocolChain(chainName) - ? validator - : new AgentGCPKey( - agentConfig.runEnv, - agentConfig.context, - Role.Validator, - chainName, - index, - ); + let chainSigner; + if (isEthereumProtocolChain(chainName)) { + chainSigner = validator; + } else { + debugLog(`Retrieving GCP key for ${chainName}, as it is not EVM`); + chainSigner = new AgentGCPKey( + agentConfig.runEnv, + agentConfig.context, + Role.Validator, + chainName, + index, + ); + } return { validator, @@ -302,6 +316,7 @@ export function getValidatorKeysForChain( export async function createAgentKeysIfNotExists( agentConfig: AgentContextConfig, ) { + debugLog('Creating agent keys if none exist'); const keys = getAllCloudAgentKeys(agentConfig); await Promise.all( @@ -318,6 +333,7 @@ export async function createAgentKeysIfNotExists( } export async function deleteAgentKeys(agentConfig: AgentContextConfig) { + debugLog('Deleting agent keys'); const keys = getAllCloudAgentKeys(agentConfig); await Promise.all(keys.map((key) => key.delete())); await execCmd( @@ -333,6 +349,7 @@ export async function rotateKey( role: Role, chainName: ChainName, ) { + debugLog(`Rotating key for ${role} on ${chainName}`); const key = getCloudAgentKey(agentConfig, role, chainName); await key.update(); const keyIdentifier = key.identifier; @@ -357,6 +374,9 @@ async function persistAddresses( context: Contexts, keys: KeyAsAddress[], ) { + debugLog( + `Persisting addresses to GCP for ${context} context in ${environment} environment`, + ); await setGCPSecret( addressesIdentifier(environment, context), JSON.stringify(keys), @@ -371,6 +391,9 @@ async function fetchGCPKeyAddresses( environment: DeployEnvironment, context: Contexts, ) { + debugLog( + `Fetching addresses from GCP for ${context} context in ${environment} environment`, + ); const addresses = await fetchGCPSecret( addressesIdentifier(environment, context), ); diff --git a/typescript/infra/src/utils/gcloud.ts b/typescript/infra/src/utils/gcloud.ts index ef2650d357..dde9411ed0 100644 --- a/typescript/infra/src/utils/gcloud.ts +++ b/typescript/infra/src/utils/gcloud.ts @@ -1,3 +1,4 @@ +import debug from 'debug'; import fs from 'fs'; import { rm, writeFile } from 'fs/promises'; @@ -9,6 +10,8 @@ interface IamCondition { expression: string; } +const debugLog = debug('infra:utils:gcloud'); + // Allows secrets to be overridden via environment variables to avoid // gcloud calls. This is particularly useful for running commands in k8s, // where we can use external-secrets to fetch secrets from GCP secret manager, @@ -23,7 +26,7 @@ export async function fetchGCPSecret( const envVarOverride = tryGCPSecretFromEnvVariable(secretName); if (envVarOverride !== undefined) { - console.log( + debugLog( `Using environment variable instead of GCP secret with name ${secretName}`, ); output = envVarOverride; @@ -41,7 +44,7 @@ export async function fetchGCPSecret( // If the environment variable GCP_SECRET_OVERRIDES_ENABLED is `true`, // this will attempt to find an environment variable of the form: -// `GCP_SECRET_OVERRIDE_${gcpSecretName..replaceAll('-', '_').toUpperCase()}` +// `GCP_SECRET_OVERRIDE_${gcpSecretName.replaceAll('-', '_').toUpperCase()}` // If found, it's returned, otherwise, undefined is returned. function tryGCPSecretFromEnvVariable(gcpSecretName: string) { const overridingEnabled = @@ -58,9 +61,12 @@ function tryGCPSecretFromEnvVariable(gcpSecretName: string) { export async function gcpSecretExists(secretName: string) { const fullName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; + debugLog(`Checking if GCP secret exists for ${fullName}`); + const matches = await execCmdAndParseJson( `gcloud secrets list --filter name=${fullName} --format json`, ); + debugLog(`Matches: ${matches.length}`); return matches.length > 0; } @@ -80,10 +86,12 @@ export async function setGCPSecret( await execCmd( `gcloud secrets create ${secretName} --data-file=${fileName} --replication-policy=automatic --labels=${labelString}`, ); + debugLog(`Created new GCP secret for ${secretName}`); } else { await execCmd( `gcloud secrets versions add ${secretName} --data-file=${fileName}`, ); + debugLog(`Added new version to existing GCP secret for ${secretName}`); } await rm(fileName); } @@ -95,6 +103,9 @@ export async function createServiceAccountIfNotExists( let serviceAccountInfo = await getServiceAccountInfo(serviceAccountName); if (!serviceAccountInfo) { serviceAccountInfo = await createServiceAccount(serviceAccountName); + debugLog(`Created new service account with name ${serviceAccountName}`); + } else { + debugLog(`Service account with name ${serviceAccountName} already exists`); } return serviceAccountInfo.email; } @@ -110,6 +121,7 @@ export async function grantServiceAccountRoleIfNotExists( matchedBinding && iamConditionsEqual(condition, matchedBinding.condition) ) { + debugLog(`Service account ${serviceAccountEmail} already has role ${role}`); return; } await execCmd( @@ -119,6 +131,7 @@ export async function grantServiceAccountRoleIfNotExists( : '' }`, ); + debugLog(`Granted role ${role} to service account ${serviceAccountEmail}`); } export async function createServiceAccountKey(serviceAccountEmail: string) { @@ -128,12 +141,14 @@ export async function createServiceAccountKey(serviceAccountEmail: string) { ); const key = JSON.parse(fs.readFileSync(localKeyFile, 'utf8')); fs.rmSync(localKeyFile); + debugLog(`Created new service account key for ${serviceAccountEmail}`); return key; } // The alphanumeric project name / ID export async function getCurrentProject() { const [result] = await execCmd('gcloud config get-value project'); + debugLog(`Current GCP project ID: ${result.trim()}`); return result.trim(); } @@ -150,10 +165,12 @@ async function getIamMemberPolicyBindings(memberEmail: string) { const unprocessedRoles = await execCmdAndParseJson( `gcloud projects get-iam-policy $(gcloud config get-value project) --format "json(bindings)" --flatten="bindings[].members" --filter="bindings.members:${memberEmail}"`, ); - return unprocessedRoles.map((unprocessedRoleObject: any) => ({ + const bindings = unprocessedRoles.map((unprocessedRoleObject: any) => ({ role: unprocessedRoleObject.bindings.role, condition: unprocessedRoleObject.bindings.condition, })); + debugLog(`Retrieved IAM policy bindings for ${memberEmail}`); + return bindings; } async function createServiceAccount(serviceAccountName: string) { @@ -169,8 +186,10 @@ async function getServiceAccountInfo(serviceAccountName: string) { `gcloud iam service-accounts list --format json --filter displayName="${serviceAccountName}"`, ); if (matches.length === 0) { + debugLog(`No service account found with name ${serviceAccountName}`); return undefined; } + debugLog(`Found service account with name ${serviceAccountName}`); return matches[0]; }