Skip to content

Commit

Permalink
feat(deploy,restart): handle exit-on option to end deployment logs
Browse files Browse the repository at this point in the history
  • Loading branch information
aurrelhebert committed Jun 27, 2024
1 parent 521755c commit f2e1981
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 15 deletions.
7 changes: 4 additions & 3 deletions bin/clever.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const Parsers = require('../src/parsers.js');
const handleCommandPromise = require('../src/command-promise-handler.js');
const Formatter = require('../src/models/format-string.js');
const { AVAILABLE_ZONES } = require('../src/models/application.js');
const { getOutputFormatOption, getSameCommitPolicyOption } = require('../src/command-options.js');
const { getOutputFormatOption, getSameCommitPolicyOption, getExitOnOption } = require('../src/command-options.js');

// Exit cleanly if the program we pipe to exits abruptly
process.stdout.on('error', (error) => {
Expand Down Expand Up @@ -246,6 +246,7 @@ function run () {
aliases: ['f'],
description: 'Force deploy even if it\'s not fast-forwardable',
}),
exitOnDeploy: getExitOnOption(),
sameCommitPolicy: getSameCommitPolicyOption(),
webhookFormat: cliparse.option('format', {
metavar: 'format',
Expand Down Expand Up @@ -713,7 +714,7 @@ function run () {
const deploy = lazyRequirePromiseModule('../src/commands/deploy.js');
const deployCommand = cliparse.command('deploy', {
description: 'Deploy an application',
options: [opts.alias, opts.branch, opts.gitTag, opts.quiet, opts.forceDeploy, opts.followDeployLogs, opts.sameCommitPolicy],
options: [opts.alias, opts.branch, opts.gitTag, opts.quiet, opts.forceDeploy, opts.followDeployLogs, opts.sameCommitPolicy, opts.exitOnDeploy],
}, deploy('deploy'));

// DIAG COMMAND
Expand Down Expand Up @@ -975,7 +976,7 @@ function run () {
const restart = lazyRequirePromiseModule('../src/commands/restart.js');
const restartCommand = cliparse.command('restart', {
description: 'Start or restart an application',
options: [opts.alias, opts.appIdOrName, opts.commit, opts.withoutCache, opts.quiet, opts.followDeployLogs],
options: [opts.alias, opts.appIdOrName, opts.commit, opts.withoutCache, opts.quiet, opts.followDeployLogs, opts.exitOnDeploy],
}, restart('restart'));

// SCALE COMMAND
Expand Down
19 changes: 19 additions & 0 deletions src/command-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,26 @@ function getSameCommitPolicyOption () {
});
}

function getExitOnOption () {
const availableExitOn = ['deploy-start', 'deploy-end', 'never'];
return cliparse.option('exit-on', {
aliases: ['e'],
metavar: 'exiton',
parser: (exitOnStrategy) => {
return availableExitOn.includes(exitOnStrategy)
? cliparse.parsers.success(exitOnStrategy)
: cliparse.parsers.error(`The exit-on strategy must be one of ${availableExitOn.join(', ')}`);
},
default: 'deploy-end',
description: `Step at which the logs streaming is ended, steps are: ${availableExitOn.join(', ')}`,
complete () {
return cliparse.autocomplete.words(availableExitOn);
},
});
}

module.exports = {
getOutputFormatOption,
getSameCommitPolicyOption,
getExitOnOption,
};
15 changes: 9 additions & 6 deletions src/commands/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ const Log = require('../models/log-v4.js');
const Logger = require('../logger.js');
const { getAllDeployments } = require('@clevercloud/client/cjs/api/v2/application.js');
const { sendToApi } = require('../models/send-to-api.js');
const ExitStrategy = require('../models/exit-strategy-option.js');

// Once the API call to redeploy() has been triggered successfully,
// the rest (waiting for deployment state to evolve and displaying logs) is done with auto retry (resilient to network failures)
async function deploy (params) {
const { alias, branch: branchName, tag: tagName, quiet, force, follow, 'same-commit-policy': sameCommitPolicy } = params.options;
const { alias, branch: branchName, tag: tagName, quiet, force, follow, 'same-commit-policy': sameCommitPolicy, 'exit-on': exitOnDeploy } = params.options;

const exitStrategy = ExitStrategy.get(follow, exitOnDeploy);

const appData = await AppConfig.getAppDetails({ alias });
const { ownerId, appId } = appData;
Expand All @@ -36,9 +39,9 @@ async function deploy (params) {
Logger.println(`The clever-cloud application is up-to-date (${colors.green(remoteHeadCommitId)})`);
return;
case 'restart':
return restartOnSameCommit(ownerId, appId, commitIdToPush, quiet, follow, false);
return restartOnSameCommit(ownerId, appId, commitIdToPush, quiet, false, exitStrategy);
case 'rebuild':
return restartOnSameCommit(ownerId, appId, commitIdToPush, quiet, follow, true);
return restartOnSameCommit(ownerId, appId, commitIdToPush, quiet, true, exitStrategy);
case 'error':
default: {
const upToDateMessage = `The clever-cloud application is up-to-date (${colors.green(remoteHeadCommitId)}).\nYou can set a policy with 'same-commit-policy' to handle differently when remote HEAD has the same commit as the one to push.\nOr try this command to restart the application:`;
Expand Down Expand Up @@ -79,12 +82,12 @@ async function deploy (params) {
});
Logger.println(colors.bold.green('Your source code has been pushed to Clever Cloud.'));

return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, commitId: commitIdToPush, knownDeployments, quiet, follow });
return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, commitId: commitIdToPush, knownDeployments, quiet, exitStrategy });
}

async function restartOnSameCommit (ownerId, appId, commitIdToPush, quiet, follow, withoutCache) {
async function restartOnSameCommit (ownerId, appId, commitIdToPush, quiet, withoutCache, exitStrategy) {
const restart = await Application.redeploy(ownerId, appId, commitIdToPush, withoutCache);
return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, deploymentId: restart.deploymentId, quiet, follow });
return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, deploymentId: restart.deploymentId, quiet, exitStrategy });
}

async function getBranchToDeploy (branchName, tagName) {
Expand Down
7 changes: 5 additions & 2 deletions src/commands/restart.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ const Application = require('../models/application.js');
const git = require('../models/git.js');
const Log = require('../models/log-v4.js');
const Logger = require('../logger.js');
const ExitStrategy = require('../models/exit-strategy-option.js');

// Once the API call to redeploy() has been triggerred successfully,
// the rest (waiting for deployment state to evolve and displaying logs) is done with auto retry (resilient to network pb)
async function restart (params) {
const { alias, app: appIdOrName, quiet, commit, 'without-cache': withoutCache, follow } = params.options;
const { alias, app: appIdOrName, quiet, commit, 'without-cache': withoutCache, follow, 'exit-on': exitOnDeploy } = params.options;

const exitStrategy = ExitStrategy.get(follow, exitOnDeploy);

const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
const fullCommitId = await git.resolveFullCommitId(commit);
Expand Down Expand Up @@ -38,8 +41,8 @@ async function restart (params) {
appId,
deploymentId: redeploy.deploymentId,
quiet,
follow,
redeployDate,
exitStrategy,
});
}

Expand Down
24 changes: 24 additions & 0 deletions src/models/exit-strategy-option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const colors = require('colors/safe');
const Logger = require('../logger.js');

function get (follow, exitOnDeploy) {
if (follow) {
if (exitOnDeploy === 'deploy-start') {
throw new Error('The `follow` and `exit-on` set to "deploy-start" options are not compatible');
}
Logger.println(colors.yellow('The `follow` option is deprecated and will be removed in an upcoming major, use --exit-on set to "never" instead'));
return 'never';
}
return exitOnDeploy;
}

// plotQuietWarning: If in quiet mode and exitStrategy set to never plot a warning to indicate that the command will end
function plotQuietWarning (exitStrategy, quiet) {
if (exitStrategy === 'never' && quiet) {
Logger.println(colors.bold.yellow('The "never" exit-on strategy is not compatible with the "quiet" mode, it will exit once the deployment ends'));
}
}

module.exports = { get, plotQuietWarning };
10 changes: 8 additions & 2 deletions src/models/log-v4.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Logger = require('../logger.js');
const { waitForDeploymentEnd, waitForDeploymentStart } = require('./deployments.js');
const { ApplicationLogStream } = require('@clevercloud/client/cjs/streams/application-logs.js');
const { JsonArray } = require('./json-array.js');
const ExitStrategy = require('../models/exit-strategy-option.js');

// 2000 logs per 100ms maximum
const THROTTLE_ELEMENTS = 2000;
Expand Down Expand Up @@ -91,15 +92,20 @@ async function watchDeploymentAndDisplayLogs (options) {
commitId,
knownDeployments,
quiet,
follow,
redeployDate,
exitStrategy,
} = options;

ExitStrategy.plotQuietWarning(exitStrategy, quiet);
// If in quiet mode, we only log start/finished deployment messages
!quiet && Logger.println('Waiting for deployment to start…');
const deployment = await waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments });
Logger.println(colors.bold.blue(`Deployment started (${deployment.uuid})`));

if (exitStrategy === 'deploy-start') {
return;
}

const deferred = new Deferred();
let logsStream;

Expand All @@ -121,7 +127,7 @@ async function watchDeploymentAndDisplayLogs (options) {
deferred.promise,
]);

if (!quiet && !follow) {
if (!quiet && exitStrategy !== 'never') {
logsStream.close(quiet ? 'quiet' : 'follow');
}

Expand Down
11 changes: 9 additions & 2 deletions src/models/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { getOldLogs } = require('@clevercloud/client/cjs/api/v2/log.js');
const { LogsStream } = require('@clevercloud/client/cjs/streams/logs.node.js');
const { sendToApi, getHostAndTokens } = require('./send-to-api.js');
const { waitForDeploymentEnd, waitForDeploymentStart } = require('./deployments.js');
const ExitStrategy = require('../models/exit-strategy-option.js');

function isCleverMessage (line) {
return line._source.syslog_program === '/home/bas/rubydeployer/deployer.rb';
Expand Down Expand Up @@ -100,12 +101,18 @@ async function displayLogs ({ appAddonId, until, since, filter, deploymentId })
return deferred.promise;
}

async function watchDeploymentAndDisplayLogs ({ ownerId, appId, deploymentId, commitId, knownDeployments, quiet, follow }) {
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(colors.bold.blue(`Deployment started (${deployment.uuid})`));

if (exitStrategy === 'deploy-start') {
return;
}

const deferred = new Deferred();
let logsStream;

Expand All @@ -127,7 +134,7 @@ async function watchDeploymentAndDisplayLogs ({ ownerId, appId, deploymentId, co
deferred.promise,
]);

if (!quiet && !follow) {
if (!quiet && exitStrategy !== 'never') {
logsStream.close();
}

Expand Down

0 comments on commit f2e1981

Please sign in to comment.