Skip to content

Commit

Permalink
feat(features): add experimental features management
Browse files Browse the repository at this point in the history
  • Loading branch information
davlgd committed Jul 1, 2024
1 parent 9dffeb5 commit a754de4
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 7 deletions.
38 changes: 32 additions & 6 deletions bin/clever.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ 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, getExitOnOption } = require('../src/command-options.js');
const { loadFeaturesConf } = require('../src/models/configuration.js');

// Exit cleanly if the program we pipe to exits abruptly
process.stdout.on('error', (error) => {
Expand Down Expand Up @@ -96,7 +97,7 @@ const Notification = lazyRequire('../src/models/notification.js');
const NetworkGroup = lazyRequire('../src/models/networkgroup.js');
const Namespaces = lazyRequire('../src/models/namespaces.js');

function run () {
async function run () {

// ARGUMENTS
const args = {
Expand All @@ -123,6 +124,7 @@ function run () {
complete: Drain('listDrainTypes'),
}),
drainUrl: cliparse.argument('drain-url', { description: 'Drain URL' }),
featureName: cliparse.argument('feature-name', { description: 'Experimental feature name' }),
fqdn: cliparse.argument('fqdn', { description: 'Domain name of the application' }),
notificationName: cliparse.argument('name', { description: 'Notification name' }),
notificationId: cliparse.argument('notification-id', { description: 'Notification ID' }),
Expand Down Expand Up @@ -1124,10 +1126,29 @@ function run () {
console.info('clever database backups download');
});

// FEATURES COMMANDS
const features = lazyRequirePromiseModule('../src/commands/features.js');
const listFeaturesCommand = cliparse.command('list', {
description: 'List available experimental features',
options: [opts.humanJsonOutputFormat],
}, features('list'));
const enableFeatureCommand = cliparse.command('enable', {
description: 'Enable an experimental feature',
args: [args.featureName],
}, features('enable'));
const disableFeatureCommand = cliparse.command('disable', {
description: 'Disable an experimental feature',
args: [args.featureName],
}, features('disable'));
const featuresCommands = cliparse.command('features', {
description: 'Manage experimental features',
commands: [listFeaturesCommand, enableFeatureCommand, disableFeatureCommand],
});

// Patch help command description
cliparseCommands.helpCommand.description = 'Display help about the Clever Cloud CLI';

const commands = _sortBy([
let commands = [
accesslogsCommand,
activityCommand,
addonCommands,
Expand All @@ -1146,14 +1167,12 @@ function run () {
drainCommands,
emailNotificationsCommand,
envCommands,
featuresCommands,
cliparseCommands.helpCommand,
loginCommand,
logoutCommand,
logsCommand,
makeDefaultCommand,
// Not ready for stable release yet
// networkGroupsCommand,
// ngCommand,
openCommand,
consoleCommand,
profileCommand,
Expand All @@ -1167,7 +1186,14 @@ function run () {
tcpRedirsCommands,
versionCommand,
webhooksCommand,
], 'name');
];

// Add experimental features only if they are enabled through the configuration file
const featuresFromConf = await loadFeaturesConf();
featuresFromConf.ng === true && commands.push(networkGroupsCommand);

// We sort the commands by name
commands = _sortBy(commands, 'name');

// CLI PARSER
const cliParser = cliparse.cli({
Expand Down
47 changes: 47 additions & 0 deletions src/commands/features.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const { getFeatures, setFeature } = require('../models/configuration.js');
const { AVAILABLE_FEATURES } = require('../models/features.js');
const Logger = require('../logger.js');

async function list (params) {
const { format } = params.options;
const features = await getFeatures();

switch (format) {
case 'json': {
Logger.printJson(features);
break;
}
case 'human':
default: {
for (const feature in features) {
console.log(`- ${feature}: ${features[feature]}`);
}
}
}
}

async function enable (params) {
const { 'feature-name': featureName } = params.namedArgs;

if (!AVAILABLE_FEATURES.includes(featureName)) {
throw new Error(`Feature '${featureName}' is not available`);
}

await setFeature(featureName, true);
Logger.println(`Experimental feature '${featureName}' enabled`);
}

async function disable (params) {
const { 'feature-name': featureName } = params.namedArgs;

if (!AVAILABLE_FEATURES.includes(featureName)) {
throw new Error(`Feature '${featureName}' is not available`);
}

await setFeature(featureName, false);
Logger.println(`Experimental feature '${featureName}' disabled`);
}

module.exports = { disable, enable, list };
38 changes: 37 additions & 1 deletion src/models/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const env = commonEnv(Logger);

const CONFIG_FILES = {
MAIN: 'clever-tools.json',
FEATURES: 'clever-tools-features.json',
IDS_CACHE: 'ids-cache.json',
};

Expand Down Expand Up @@ -61,6 +62,40 @@ async function writeOAuthConf (oauthData) {
}
}

async function loadFeaturesConf () {
Logger.debug('Load features configuration from ' + conf.FEATURES_FILE);
try {
const rawFile = await fs.readFile(conf.FEATURES_FILE);
return JSON.parse(rawFile);
}
catch (error) {
Logger.info(`Cannot load experimental features configuration from ${conf.FEATURES_FILE}`);
return {};
}
}

async function getFeatures () {
Logger.debug('Get features configuration from ' + conf.FEATURES_FILE);
try {
const rawFile = await fs.readFile(conf.FEATURES_FILE);
return JSON.parse(rawFile);
}
catch (error) {
throw new Error(`Cannot get experimental features configuration from ${conf.FEATURES_FILE}`);
}
}

async function setFeature (feature, value) {
const currentFeatures = await getFeatures();
const newFeatures = { ...currentFeatures, ...{ [feature]: value } };
try {
await fs.writeFile(conf.FEATURES_FILE, JSON.stringify(newFeatures, null, 2));
}
catch (error) {
throw new Error(`Cannot write experimental features configuration to ${conf.FEATURES_FILE}`);
}
}

async function loadIdsCache () {
const cachePath = getConfigPath(CONFIG_FILES.IDS_CACHE);
try {
Expand Down Expand Up @@ -100,11 +135,12 @@ const conf = env.getOrElseAll({
SSH_GATEWAY: 'ssh@sshgateway-clevercloud-customers.services.clever-cloud.com',

CONFIGURATION_FILE: getConfigPath(CONFIG_FILES.MAIN),
FEATURES_FILE: getConfigPath(CONFIG_FILES.FEATURES),
CONSOLE_TOKEN_URL: 'https://console.clever-cloud.com/cli-oauth',
// CONSOLE_TOKEN_URL: 'https://next-console.cleverapps.io/cli-oauth',

CLEVER_CONFIGURATION_DIR: path.resolve('.', 'clevercloud'),
APP_CONFIGURATION_FILE: path.resolve('.', '.clever.json'),
});

module.exports = { conf, loadOAuthConf, writeOAuthConf, loadIdsCache, writeIdsCache };
module.exports = { conf, loadOAuthConf, writeOAuthConf, loadFeaturesConf, getFeatures, setFeature, loadIdsCache, writeIdsCache };
5 changes: 5 additions & 0 deletions src/models/features.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const AVAILABLE_FEATURES = ['faas', 'kv', 'ng'];

module.exports = { AVAILABLE_FEATURES };

0 comments on commit a754de4

Please sign in to comment.