clever-tools
Version:
Command Line Interface for Clever Cloud.
144 lines (119 loc) • 5.09 kB
JavaScript
import { getOldLogs } from '@clevercloud/client/esm/api/v2/log.js';
import { LogsStream } from '@clevercloud/client/esm/streams/logs.node.js';
import _ from 'lodash';
import { styleText } from '../lib/style-text.js';
import { Logger } from '../logger.js';
import { waitForDeploymentEnd, waitForDeploymentStart } from './deployments.js';
import * as ExitStrategy from './exit-strategy-option.js';
import { getHostAndTokens, sendToApi } from './send-to-api.js';
import { Deferred } from './utils.js';
function isCleverMessage(line) {
return line._source.syslog_program === '/home/bas/rubydeployer/deployer.rb';
}
function isDeploymentSuccessMessage(line) {
return isCleverMessage(line) && _.startsWith(line._source['@message'].toLowerCase(), 'successfully deployed in');
}
function isDeploymentFailedMessage(line) {
return isCleverMessage(line) && _.startsWith(line._source['@message'].toLowerCase(), 'deploy failed in');
}
function isBuildSucessMessage(line) {
return isCleverMessage(line) && _.startsWith(line._source['@message'].toLowerCase(), 'build succeeded in');
}
function formatLogLine(line) {
const { '@timestamp': timestamp, '@message': message } = line._source;
if (isDeploymentSuccessMessage(line)) {
return `${timestamp}: ${styleText(['bold', 'green'], message)}`;
} else if (isDeploymentFailedMessage(line)) {
return `${timestamp}: ${styleText(['bold', 'red'], message)}`;
} else if (isBuildSucessMessage(line)) {
return `${timestamp}: ${styleText(['bold', 'blue'], message)}`;
}
return `${timestamp}: ${message}`;
}
async function displayLiveLogs({ appId, filter, until, deploymentId }, deferred) {
const { apiHost, tokens } = await getHostAndTokens();
const logsStream = new LogsStream({ apiHost, tokens, appId, filter, deploymentId });
logsStream
.on('open', () => Logger.debug('SSE for logs (open) ' + JSON.stringify({ appId, filter, deploymentId })))
.on('log', (line) => {
const { '@timestamp': timestamp } = line._source;
if (until != null && new Date(timestamp) > until) {
logsStream.close();
} else {
Logger.println(formatLogLine(line));
}
})
.on('ping', () => Logger.debug('SSE for logs (ping)'))
.on('close', ({ reason }) => Logger.debug('SSE for logs (close) ' + reason))
.on('error', (error) => deferred.reject(error));
logsStream.open({ autoRetry: true, maxRetryCount: 6 });
return logsStream;
}
export async function displayLogs({ appAddonId, until, since, filter, deploymentId }) {
const now = new Date();
const fetchOldLogs = since == null || since < now;
if (fetchOldLogs) {
const oldLogs = await getOldLogs({
appId: appAddonId,
before: until != null ? until.toISOString() : null,
after: since != null ? since.toISOString() : null,
filter,
deployment_id: deploymentId,
}).then(sendToApi);
for (const line of oldLogs.reverse()) {
Logger.println(formatLogLine(line));
}
}
// No need to fetch live logs if until date is in the past
if (until != null && until < now) {
return;
}
const deferred = new Deferred();
await displayLiveLogs({ appId: appAddonId, filter, deploymentId, until }, deferred);
return deferred.promise;
}
export async function watchDeploymentAndDisplayLogs({
ownerId,
appId,
deploymentId,
commitId,
knownDeployments,
quiet,
exitStrategy,
}) {
ExitStrategy.plotQuietWarning(exitStrategy, quiet);
// If in quiet mode, we only log start/finished deployment messages
Logger.println('Waiting for deployment to start…');
const deployment = await waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments });
Logger.println(styleText(['bold', 'blue'], `Deployment started (${deployment.uuid})`));
if (exitStrategy === 'deploy-start') {
return;
}
const deferred = new Deferred();
let logsStream;
if (!quiet) {
// About the deferred…
// If displayLiveLogs() throws an error,
// the async function we're in (watchDeploymentAndDisplayLogs) will stop here and the error will be passed to the parent.
// displayLiveLogs() defines callback listeners so if it catches error in those callbacks,
// it has no proper way to bubble up the error here.
// Using the deferred enables this.
logsStream = await displayLiveLogs({ appId, deploymentId: deployment.uuid }, deferred);
}
Logger.println('Waiting for application logs…');
// Wait for deployment end (or an error thrown by logs with the deferred)
const deploymentEnded = await Promise.race([
waitForDeploymentEnd({ ownerId, appId, deploymentId: deployment.uuid }),
deferred.promise,
]);
if (!quiet && exitStrategy !== 'never') {
logsStream.close();
}
if (deploymentEnded.state === 'OK') {
Logger.println(styleText(['bold', 'green'], 'Deployment successful'));
} else if (deploymentEnded.state === 'CANCELLED') {
throw new Error('Deployment was cancelled. Please check the activity');
} else {
throw new Error('Deployment failed. Please check the logs');
}
}