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

feat: emit all shell logging to publishProgressListener #295

Merged
merged 29 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3b7636d
remove quiet and improve progress listener
HBobertz Jan 9, 2025
2613c7b
update comments
HBobertz Jan 9, 2025
7a23ef0
remove unnecessary logic
HBobertz Jan 9, 2025
2cb1586
update comment
HBobertz Jan 9, 2025
2a94253
address feedback
HBobertz Jan 10, 2025
fcaeecd
shell event publisher
HBobertz Jan 13, 2025
e2bd0d0
remove unecessary type
HBobertz Jan 13, 2025
2194b76
refactor unecessary code
HBobertz Jan 13, 2025
0560603
remove loglevels export
HBobertz Jan 13, 2025
08e307d
address feedback
HBobertz Jan 13, 2025
a58f0a5
add type tsdoc
HBobertz Jan 13, 2025
663a5fc
address feedback
HBobertz Jan 13, 2025
c36b7f8
add documentation to events
HBobertz Jan 13, 2025
da98322
remoe unecessary export
HBobertz Jan 13, 2025
c510731
add buffer to handle shell output
HBobertz Jan 13, 2025
d929b8a
fix ecr logger being irrelevant
HBobertz Jan 14, 2025
4d8c044
rename logger to event emitter
HBobertz Jan 14, 2025
feadde2
address feedback
HBobertz Jan 14, 2025
44865d4
address feedback
HBobertz Jan 14, 2025
0b49399
fix white space
HBobertz Jan 14, 2025
ac1b0f2
address docker feedback
HBobertz Jan 19, 2025
6cafe1d
better subprocess destination handling
HBobertz Jan 20, 2025
9a89c65
address feedback
HBobertz Jan 20, 2025
a9cd99b
address feedback
HBobertz Jan 21, 2025
d81d42a
Update lib/private/docker.ts
HBobertz Jan 21, 2025
08aa6df
Update lib/private/docker.ts
HBobertz Jan 21, 2025
fea38d9
address feedback
HBobertz Jan 21, 2025
f849578
add default of empty
HBobertz Jan 21, 2025
55d4523
Merge branch 'main' into bobertzh/shell-log-capture
HBobertz Jan 21, 2025
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
13 changes: 8 additions & 5 deletions bin/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import * as fs from 'fs';
import * as path from 'path';

export type LogLevel = 'verbose' | 'info' | 'error';
let logThreshold: LogLevel = 'info';
export let logThreshold: LogLevel = 'info';
HBobertz marked this conversation as resolved.
Show resolved Hide resolved

export const VERSION = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', 'package.json'), { encoding: 'utf-8' })
).version;

const LOG_LEVELS: Record<LogLevel, number> = {
export const LOG_LEVELS: Record<LogLevel, number> = {
HBobertz marked this conversation as resolved.
Show resolved Hide resolved
verbose: 1,
info: 2,
error: 3,
Expand All @@ -18,9 +18,12 @@ export function setLogThreshold(threshold: LogLevel) {
logThreshold = threshold;
}

export function log(level: LogLevel, message: string) {
export function log(level: LogLevel, message: string, forceStdOut = false) {
if (LOG_LEVELS[level] >= LOG_LEVELS[logThreshold]) {
// eslint-disable-next-line no-console
console.error(`${level.padEnd(7, ' ')}: ${message}`);
if (forceStdOut) {
console.log(`${level.padEnd(7, ' ')}: ${message}`);
} else {
console.error(`${level.padEnd(7, ' ')}: ${message}`);
}
}
}
8 changes: 4 additions & 4 deletions bin/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function publish(args: { path: string; assets?: string[]; profile?:
}
}

const EVENT_TO_LEVEL: Record<EventType, LogLevel> = {
export const EVENT_TO_LEVEL: Record<EventType, LogLevel> = {
HBobertz marked this conversation as resolved.
Show resolved Hide resolved
build: 'verbose',
cached: 'verbose',
check: 'verbose',
Expand All @@ -49,8 +49,8 @@ const EVENT_TO_LEVEL: Record<EventType, LogLevel> = {
upload: 'verbose',
};

class ConsoleProgress implements IPublishProgressListener {
public onPublishEvent(type: EventType, event: IPublishProgress): void {
log(EVENT_TO_LEVEL[type], `[${event.percentComplete}%] ${type}: ${event.message}`);
export class ConsoleProgress implements IPublishProgressListener {
public onPublishEvent(type: EventType, event: IPublishProgress, forceStdOut?: boolean): void {
HBobertz marked this conversation as resolved.
Show resolved Hide resolved
log(EVENT_TO_LEVEL[type], `[${event.percentComplete}%] ${type}: ${event.message}`, forceStdOut);
}
}
7 changes: 0 additions & 7 deletions lib/private/asset-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,3 @@ export interface IHandlerHost {

emitMessage(type: EventType, m: string): void;
}

export interface IHandlerOptions {
/**
* Suppress all output
*/
readonly quiet?: boolean;
}
9 changes: 5 additions & 4 deletions lib/private/docker-credentials.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { Logger } from './shell';
import { EventPublisher } from './shell';
import { IAws, IECRClient } from '../aws';
import { EventType } from '../progress';

export interface DockerCredentials {
readonly Username: string;
Expand Down Expand Up @@ -106,9 +107,9 @@ export async function fetchDockerLoginCredentials(
}
}

export async function obtainEcrCredentials(ecr: IECRClient, logger?: Logger) {
if (logger) {
logger('Fetching ECR authorization token');
export async function obtainEcrCredentials(ecr: IECRClient, eventPublisher?: EventPublisher) {
if (eventPublisher) {
eventPublisher(EventType.DEBUG, 'Fetching ECR authorization token');
}

const authData = (await ecr.getAuthorizationToken()).authorizationData || [];
Expand Down
20 changes: 10 additions & 10 deletions lib/private/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { cdkCredentialsConfig, obtainEcrCredentials } from './docker-credentials';
import { Logger, shell, ShellOptions, ProcessFailedError } from './shell';
import { EventPublisher, shell, ShellOptions, ProcessFailedError } from './shell';
import { createCriticalSection } from './util';
import { IECRClient } from '../aws';

Expand All @@ -24,12 +24,10 @@ interface BuildOptions {
readonly cacheFrom?: DockerCacheOption[];
readonly cacheTo?: DockerCacheOption;
readonly cacheDisabled?: boolean;
readonly quiet?: boolean;
}

interface PushOptions {
readonly tag: string;
readonly quiet?: boolean;
}

export interface DockerCredentialsConfig {
Expand All @@ -55,14 +53,14 @@ export interface DockerCacheOption {
export class Docker {
private configDir: string | undefined = undefined;

constructor(private readonly logger?: Logger) {}
constructor(private readonly eventPublisher: EventPublisher) {}

/**
* Whether an image with the given tag exists
*/
public async exists(tag: string) {
try {
await this.execute(['inspect', tag], { quiet: true });
await this.execute(['inspect', tag], { eventPublisher: this.eventPublisher });
return true;
} catch (e: any) {
const error: ProcessFailedError = e;
Expand Down Expand Up @@ -123,7 +121,7 @@ export class Docker {
];
await this.execute(buildCommand, {
cwd: options.directory,
quiet: options.quiet,
eventPublisher: this.eventPublisher,
});
}

Expand All @@ -143,16 +141,19 @@ export class Docker {
// 'WARNING! Your password will be stored unencrypted'
// doesn't really matter since it's a token.
quiet: true,
eventPublisher: this.eventPublisher,
}
);
}

public async tag(sourceTag: string, targetTag: string) {
await this.execute(['tag', sourceTag, targetTag]);
await this.execute(['tag', sourceTag, targetTag], { eventPublisher: this.eventPublisher });
}

public async push(options: PushOptions) {
await this.execute(['push', options.tag], { quiet: options.quiet });
await this.execute(['push', options.tag], {
eventPublisher: this.eventPublisher,
});
}

/**
Expand Down Expand Up @@ -194,13 +195,12 @@ export class Docker {
this.configDir = undefined;
}

private async execute(args: string[], options: ShellOptions = {}) {
private async execute(args: string[], options: ShellOptions) {
rix0rrr marked this conversation as resolved.
Show resolved Hide resolved
const configArgs = this.configDir ? ['--config', this.configDir] : [];

const pathToCdkAssets = path.resolve(__dirname, '..', '..', 'bin');
try {
await shell([getDockerCmd(), ...configArgs, ...args], {
logger: this.logger,
...options,
env: {
...process.env,
Expand Down
34 changes: 13 additions & 21 deletions lib/private/handlers/container-images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { destinationToClientOptions } from './client-options';
import { DockerImageManifestEntry } from '../../asset-manifest';
import type { IECRClient } from '../../aws';
import { EventType } from '../../progress';
import { IAssetHandler, IHandlerHost, IHandlerOptions } from '../asset-handler';
import { IAssetHandler, IHandlerHost } from '../asset-handler';
import { Docker } from '../docker';
import { replaceAwsPlaceholders } from '../placeholders';
import { shell } from '../shell';
Expand All @@ -22,8 +22,7 @@ export class ContainerImageAssetHandler implements IAssetHandler {
constructor(
private readonly workDir: string,
private readonly asset: DockerImageManifestEntry,
private readonly host: IHandlerHost,
private readonly options: IHandlerOptions
private readonly host: IHandlerHost
) {}

public async build(): Promise<void> {
Expand All @@ -46,10 +45,7 @@ export class ContainerImageAssetHandler implements IAssetHandler {
dockerForBuilding,
this.workDir,
this.asset,
this.host,
{
quiet: this.options.quiet,
}
this.host
);
const localTagName = await builder.build();

Expand All @@ -65,7 +61,7 @@ export class ContainerImageAssetHandler implements IAssetHandler {

public async isPublished(): Promise<boolean> {
try {
const initOnce = await this.initOnce({ quiet: true });
const initOnce = await this.initOnce();
return initOnce.destinationAlreadyExists;
} catch (e: any) {
this.host.emitMessage(EventType.DEBUG, `${e.message}`);
Expand Down Expand Up @@ -94,20 +90,17 @@ export class ContainerImageAssetHandler implements IAssetHandler {
}

this.host.emitMessage(EventType.UPLOAD, `Push ${initOnce.imageUri}`);
await dockerForPushing.push({ tag: initOnce.imageUri, quiet: this.options.quiet });
await dockerForPushing.push({ tag: initOnce.imageUri });
}

private async initOnce(
options: { quiet?: boolean } = {}
): Promise<ContainerImageAssetHandlerInit> {
private async initOnce(): Promise<ContainerImageAssetHandlerInit> {
if (this.init) {
return this.init;
}

const destination = await replaceAwsPlaceholders(this.asset.destination, this.host.aws);
const ecr = await this.host.aws.ecrClient({
...destinationToClientOptions(destination),
quiet: options.quiet,
});
const account = async () => (await this.host.aws.discoverCurrentAccount())?.accountId;

Expand Down Expand Up @@ -152,17 +145,12 @@ export class ContainerImageAssetHandler implements IAssetHandler {
}
}

interface ContainerImageBuilderOptions {
readonly quiet?: boolean;
}

class ContainerImageBuilder {
constructor(
private readonly docker: Docker,
private readonly workDir: string,
private readonly asset: DockerImageManifestEntry,
private readonly host: IHandlerHost,
private readonly options: ContainerImageBuilderOptions
rix0rrr marked this conversation as resolved.
Show resolved Hide resolved
private readonly host: IHandlerHost
) {}

async build(): Promise<string | undefined> {
Expand Down Expand Up @@ -208,7 +196,12 @@ class ContainerImageBuilder {
return undefined;
}

return (await shell(executable, { cwd: assetPath, quiet: true })).trim();
rix0rrr marked this conversation as resolved.
Show resolved Hide resolved
return (
await shell(executable, {
cwd: assetPath,
eventPublisher: this.host.emitMessage,
HBobertz marked this conversation as resolved.
Show resolved Hide resolved
})
).trim();
}

private async buildImage(localTagName: string): Promise<void> {
Expand Down Expand Up @@ -236,7 +229,6 @@ class ContainerImageBuilder {
cacheFrom: source.cacheFrom,
cacheTo: source.cacheTo,
cacheDisabled: source.cacheDisabled,
quiet: this.options.quiet,
});
}

Expand Down
3 changes: 1 addition & 2 deletions lib/private/handlers/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export class FileAssetHandler implements IAssetHandler {
try {
const s3 = await this.host.aws.s3Client({
...destinationToClientOptions(destination),
quiet: true,
});
this.host.emitMessage(EventType.CHECK, `Check ${s3Url}`);

Expand Down Expand Up @@ -204,7 +203,7 @@ export class FileAssetHandler implements IAssetHandler {
this.host.emitMessage(EventType.BUILD, `Building asset source using command: '${executable}'`);

return {
packagedPath: (await shell(executable, { quiet: true })).trim(),
packagedPath: (await shell(executable, { eventPublisher: this.host.emitMessage })).trim(),
HBobertz marked this conversation as resolved.
Show resolved Hide resolved
contentType: 'application/zip',
};
}
Expand Down
7 changes: 3 additions & 4 deletions lib/private/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,18 @@ import {
FileManifestEntry,
type IManifestEntry,
} from '../../asset-manifest';
import type { IAssetHandler, IHandlerHost, IHandlerOptions } from '../asset-handler';
import type { IAssetHandler, IHandlerHost } from '../asset-handler';

export function makeAssetHandler(
manifest: AssetManifest,
asset: IManifestEntry,
host: IHandlerHost,
options: IHandlerOptions
host: IHandlerHost
): IAssetHandler {
if (asset instanceof FileManifestEntry) {
return new FileAssetHandler(manifest.directory, asset, host);
}
if (asset instanceof DockerImageManifestEntry) {
return new ContainerImageAssetHandler(manifest.directory, asset, host, options);
return new ContainerImageAssetHandler(manifest.directory, asset, host);
}

throw new Error(`Unrecognized asset type: '${asset}'`);
Expand Down
21 changes: 11 additions & 10 deletions lib/private/shell.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as child_process from 'child_process';
import { EventType } from '../progress';

export type Logger = (x: string) => void;
export type EventPublisher = (event: EventType, message: string, forceStdOut?: boolean) => void;

export interface ShellOptions extends child_process.SpawnOptions {
readonly quiet?: boolean;
readonly logger?: Logger;
readonly eventPublisher: EventPublisher;
readonly input?: string;
}

Expand All @@ -14,10 +15,8 @@ export interface ShellOptions extends child_process.SpawnOptions {
* Shell function which both prints to stdout and collects the output into a
* string.
*/
export async function shell(command: string[], options: ShellOptions = {}): Promise<string> {
if (options.logger) {
options.logger(renderCommandLine(command));
}
export async function shell(command: string[], options: ShellOptions): Promise<string> {
options.eventPublisher(EventType.DEBUG, renderCommandLine(command));
HBobertz marked this conversation as resolved.
Show resolved Hide resolved
const child = child_process.spawn(command[0], command.slice(1), {
...options,
stdio: [options.input ? 'pipe' : 'ignore', 'pipe', 'pipe'],
Expand All @@ -32,19 +31,21 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom
const stdout = new Array<any>();
const stderr = new Array<any>();

// Both write to stdout and collect
// Both log to debug and collect
child.stdout!.on('data', (chunk) => {
if (!options.quiet) {
process.stdout.write(chunk);
const message = Buffer.isBuffer(chunk) ? chunk.toString() : chunk;
// Emit events on stdout since we received the output on stdout
options.eventPublisher(EventType.DEBUG, message, true);
}
stdout.push(chunk);
});

child.stderr!.on('data', (chunk) => {
if (!options.quiet) {
process.stderr.write(chunk);
const message = Buffer.isBuffer(chunk) ? chunk.toString() : chunk;
options.eventPublisher(EventType.DEBUG, message);
}

stderr.push(chunk);
});

Expand Down
2 changes: 1 addition & 1 deletion lib/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface IPublishProgressListener {
/**
* Asset build event
*/
onPublishEvent(type: EventType, event: IPublishProgress): void;
onPublishEvent(type: EventType, event: IPublishProgress, forceStdOut?: boolean): void;
HBobertz marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
5 changes: 2 additions & 3 deletions lib/publishing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface AssetPublishingOptions {
* Whether to print publishing logs
*
* @default true
* @deprecated Implement a custom IPublishProgressListener with the desired behavior instead
*/
readonly quiet?: boolean;
}
Expand Down Expand Up @@ -281,9 +282,7 @@ export class AssetPublishing implements IPublishProgress {
if (existing) {
return existing;
}
const ret = makeAssetHandler(this.manifest, asset, this.handlerHost, {
quiet: this.options.quiet,
});
const ret = makeAssetHandler(this.manifest, asset, this.handlerHost);
this.handlerCache.set(asset, ret);
return ret;
}
Expand Down
Loading
Loading