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

[PoC] Network Groups initial support #780

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
221 changes: 217 additions & 4 deletions bin/clever.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import '../src/initial-setup.js';

import cliparse from 'cliparse';
import cliparseCommands from 'cliparse/src/command.js';
import colors from 'colors/safe.js';
import _sortBy from 'lodash/sortBy.js';

import { getPackageJson } from '../src/load-package-json.cjs';
Expand All @@ -13,13 +14,16 @@ import * as Parsers from '../src/parsers.js';
import { handleCommandPromise } from '../src/command-promise-handler.js';
import * as Application from '../src/models/application.js';
import { AVAILABLE_ZONES } from '../src/models/application.js';
import { getExitOnOption, getOutputFormatOption, getSameCommitPolicyOption } from '../src/command-options.js';
import { EXPERIMENTAL_FEATURES } from '../src/experimental-features.js';
import { loadFeaturesConf } from '../src/models/configuration.js';
import { getOutputFormatOption, getSameCommitPolicyOption, getExitOnOption } from '../src/command-options.js';

import * as Addon from '../src/models/addon.js';
import * as ApplicationConfiguration from '../src/models/application_configuration.js';
import * as Drain from '../src/models/drain.js';
import * as Notification from '../src/models/notification.js';
import * as Namespaces from '../src/models/namespaces.js';
import * as NetworkGroup from '../src/models/ng.js';

import * as accesslogsModule from '../src/commands/accesslogs.js';
import * as activity from '../src/commands/activity.js';
Expand All @@ -34,11 +38,15 @@ import * as diag from '../src/commands/diag.js';
import * as domain from '../src/commands/domain.js';
import * as drain from '../src/commands/drain.js';
import * as env from '../src/commands/env.js';
import * as features from '../src/commands/features.js';
import * as link from '../src/commands/link.js';
import * as login from '../src/commands/login.js';
import * as logout from '../src/commands/logout.js';
import * as logs from '../src/commands/logs.js';
import * as makeDefault from '../src/commands/makeDefault.js';
import * as ng from '../src/commands/ng.js';
import * as ngMembers from '../src/commands/ng-members.js';
import * as ngPeers from '../src/commands/ng-peers.js';
import * as notifyEmail from '../src/commands/notify-email.js';
import * as open from '../src/commands/open.js';
import * as consoleModule from '../src/commands/console.js';
Expand Down Expand Up @@ -74,10 +82,72 @@ cliparse.command = function (name, options, commandFunction) {
});
};

function run () {
// Add a yellow color and status tag to the description of an experimental command
function colorizeExperimentalCommand (command, id) {
const status = EXPERIMENTAL_FEATURES[id].status;
command.description = colors.yellow(command.description + ' [' + status.toUpperCase() + ']');
return command;
}

async function run () {

// ARGUMENTS
const args = {
// Network Groups arguments
ngId: cliparse.argument('ng-id', {
description: 'The Network Group ID',
}),
ngLabel: cliparse.argument('label', {
description: 'Network Group label, also used for DNS context',
}),
ngIdOrLabel: cliparse.argument('ng', {
description: 'Network Group ID or label',
parser: Parsers.ngIdOrLabel,
}),
ngDescription: cliparse.argument('description', {
description: 'Network Group description',
}),
ngMemberId: cliparse.argument('member-id', {
description: 'The member ID: an app ID (e.g.: \'app_xxx\'), add-on ID (e.g.: \'addon_xxx\') or external node category ID',
// complete: NetworkGroup('xxx'),
}),
ngMembersIds: cliparse.argument('members-ids', {
description: "Comma separated list of Network Group members IDs ('app_xxx', 'addon_xxx', 'external_xxx')",
parser: Parsers.commaSeparated,
}),
ngMemberDomainName: cliparse.argument('domain-name', {
description: 'Member name used in the \'<memberName>.members.<ngID>.ng.clever-cloud.com\', false domain name alias',
}),
ngPeerId: cliparse.argument('peer-id', {
description: 'The peer ID',
// complete: NetworkGroup('xxx'),
}),
ngPeerRole: cliparse.argument('role', {
description: `The peer role, (${'client'} or ${'server'})`,
parser: Parsers.ngPeerRole,
complete: NetworkGroup.listAvailablePeerRoles,
}),
// FIXME: Add "internal" member type
ngMemberType: cliparse.argument('type', {
description: `The member type (${'application'}, ${'addon'} or ${'external'})`,
parser: Parsers.ngMemberType,
complete: NetworkGroup.listAvailableMemberTypes,
}),
ngNodeCategoryId: cliparse.argument('node-category-id', {
description: 'The external node category ID',
// complete: NetworkGroup('xxx'),
}),
ngPeerLabel: cliparse.argument('label', {
description: 'Network Group peer label',
}),
ngPeerParentMemberId: cliparse.argument('parent', {
description: 'Network Group peer category ID (parent member ID)',
// complete: NetworkGroup('xxx'),
}),
ngSearchAppId: cliparse.argument('app-id', {
description: 'The app ID to search',
// complete: NetworkGroup('xxx'),
}),
addonIdOrName: cliparse.argument('addon-id', {
description: 'Add-on ID (or name, if unambiguous)',
parser: Parsers.addonIdOrName,
Expand All @@ -102,6 +172,11 @@ function run () {
}),
drainUrl: cliparse.argument('drain-url', { description: 'Drain URL' }),
fqdn: cliparse.argument('fqdn', { description: 'Domain name of the application' }),
features: cliparse.argument('features', {
description: 'Comma-separated list of experimental features to manage',
parser: Parsers.commaSeparated,
}),
featureId: cliparse.argument('feature', { description: 'Experimental feature to manage' }),
notificationName: cliparse.argument('name', { description: 'Notification name' }),
notificationId: cliparse.argument('notification-id', { description: 'Notification ID' }),
webhookUrl: cliparse.argument('url', { description: 'Webhook URL' }),
Expand All @@ -126,6 +201,35 @@ function run () {

// OPTIONS
const opts = {
// Network Groups options
ngIdOrLabel: cliparse.option('ng', {
metavar: 'ng_id_or_label',
description: 'Network Group ID or label',
parser: Parsers.ngIdOrLabel,
// complete: NetworkGroup('xxx'),
}),
ngMembersIds: cliparse.option('members-ids', {
metavar: 'members_ids',
description: "Comma separated list of Network Group members IDs ('app_xxx', 'addon_xxx', 'external_xxx')",
parser: Parsers.commaSeparated,
}),
ngDescription: cliparse.option('description', {
metavar: 'ng_description',
description: 'Network Group description',
}),
ngMemberLabel: cliparse.option('label', {
required: false,
metavar: 'member_label',
description: 'The member label',
}),
ngPeerGetConfig: cliparse.flag('config', {
description: 'Get the Wireguard configuration of an external node',
}),
wgPublicKey: cliparse.option('public-key', {
required: false,
metavar: 'public_key',
description: 'The public key of the peer',
}),
sourceableEnvVarsList: cliparse.flag('add-export', { description: 'Display sourceable env variables setting' }),
logsFormat: getOutputFormatOption(['json-stream']),
activityFormat: getOutputFormatOption(['json-stream']),
Expand Down Expand Up @@ -666,6 +770,28 @@ function run () {
commands: [envSetCommand, envRemoveCommand, envImportCommand, envImportVarsFromLocalEnvCommand],
}, env.list);

// EXPERIMENTAL FEATURES COMMAND
const listFeaturesCommand = cliparse.command('list', {
description: 'List available experimental features',
options: [opts.humanJsonOutputFormat],
}, features.list);
const helpFeaturesCommand = cliparse.command('help', {
description: 'Display help about an experimental feature',
args: [args.featureId],
}, features.help);
const enableFeatureCommand = cliparse.command('enable', {
description: 'Enable an experimental feature',
args: [args.features],
}, features.enable);
const disableFeatureCommand = cliparse.command('disable', {
description: 'Disable an experimental feature',
args: [args.features],
}, features.disable);
const featuresCommands = cliparse.command('features', {
description: 'Manage Clever Tools experimental features',
commands: [enableFeatureCommand, disableFeatureCommand, listFeaturesCommand, helpFeaturesCommand],
}, features.list);

// LINK COMMAND
const appLinkCommand = cliparse.command('link', {
description: 'Link this repo to an existing application',
Expand Down Expand Up @@ -696,6 +822,85 @@ function run () {
args: [args.alias],
}, makeDefault.makeDefault);

// NETWORK GROUPS ADD COMMANDS
const ngMembersAddCommand = cliparse.command('member', {
description: 'Add an app or add-on as a Network Group member',
args: [args.ngIdOrLabel, args.ngMemberId],
options: [opts.ngMemberLabel],
}, ngMembers.addMember);
const ngMembersAddExternalCommand = cliparse.command('external-peer', {
description: 'Add an external node as a Network Group peer',
args: [args.ngIdOrLabel, args.ngPeerLabel, args.ngPeerRole, args.ngPeerParentMemberId],
options: [opts.humanJsonOutputFormat, opts.wgPublicKey],
}, ngPeers.addExternalPeer);

// NETWORK GROUPS REMOVE COMMANDS
const ngMembersRemoveCommand = cliparse.command('member', {
description: 'Remove an app or add-on from a Network Group',
args: [args.ngMemberId],
options: [opts.ngIdOrLabel],
}, ngMembers.removeMember);
const ngPeersRemoveCommand = cliparse.command('peer', {
description: 'Remove an external node from a Network Group',
args: [args.ngPeerId],
options: [opts.ngIdOrLabel],
}, ngPeers.removeExternalPeer);

// NETWORK GROUPS LIST COMMANDS
const ngMembersListCommand = cliparse.command('members', {
description: 'List members of a Network Group',
args: [args.ngIdOrLabel],
}, ngMembers.listMembers);
const ngPeersListCommand = cliparse.command('peers', {
description: 'List peers of a Network Group',
args: [args.ngIdOrLabel],
}, ngPeers.listPeers);

// NETWORK GROUPS GET COMMANDS
const ngMembersGetCommand = cliparse.command('member', {
description: 'Get a Network Group member\'s details',
args: [args.ngMemberId],
options: [opts.naturalName, opts.ngIdOrLabel],
}, ngMembers.getMember);
const ngPeersGetCommand = cliparse.command('peer', {
description: 'Get a Network Group peer\'s details',
args: [args.ngPeerId],
options: [opts.ngPeerGetConfig, opts.ngIdOrLabel],
}, ngPeers.getPeer);

// NETWORK GROUP COMMANDS
const ngListCommand = cliparse.command('list', {
description: 'List Network Groups (default), their members or peers',
options: [opts.humanJsonOutputFormat],
commands: [ngMembersListCommand, ngPeersListCommand],
}, ng.listNg);
const ngCreateCommand = cliparse.command('add', {
description: 'Add a Network Group (default), a member or an external peer',
args: [args.ngLabel],
privateOptions: [opts.ngMembersIds, opts.ngDescription, opts.optTags],
commands: [ngMembersAddCommand, ngMembersAddExternalCommand],
}, ng.createNg);
const ngDeleteCommand = cliparse.command('remove', {
description: 'Delete a Network Group (default), a member or an external peer',
args: [args.ngIdOrLabel],
commands: [ngMembersRemoveCommand, ngPeersRemoveCommand],
}, ng.deleteNg);
const ngGetCommand = cliparse.command('get', {
description: 'Get details about a Network Group (default), a member or a peer',
args: [args.ngIdOrLabel],
options: [opts.humanJsonOutputFormat],
commands: [ngMembersGetCommand, ngPeersGetCommand],
}, ng.getNg);
const ngJoinCommand = cliparse.command('join', {
description: 'Join a Network Group (default), a member or an external peer',
args: [args.ngIdOrLabel],
}, ng.joinNg);
const networkGroupsCommand = cliparse.command('ng', {
description: 'Manage Network Groups, their members and peers',
options: [opts.orgaIdOrName],
commands: [ngListCommand, ngCreateCommand, ngDeleteCommand, ngGetCommand, ngJoinCommand],
}, ng.listNg);

// NOTIFY-EMAIL COMMAND
const addEmailNotificationCommand = cliparse.command('add', {
description: 'Add a new email notification',
Expand Down Expand Up @@ -881,7 +1086,7 @@ function run () {
// Patch help command description
cliparseCommands.helpCommand.description = 'Display help about the Clever Cloud CLI';

const commands = _sortBy([
let commands = [
accesslogsCommand,
activityCommand,
addonCommands,
Expand All @@ -900,6 +1105,7 @@ function run () {
drainCommands,
emailNotificationsCommand,
envCommands,
featuresCommands,
cliparseCommands.helpCommand,
loginCommand,
logoutCommand,
Expand All @@ -918,7 +1124,14 @@ function run () {
tcpRedirsCommands,
versionCommand,
webhooksCommand,
], 'name');
];

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

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

// CLI PARSER
const cliParser = cliparse.cli({
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ to ask for new features, enhancements or help us to provide them to our communit

You'll find below the first commands to know to connect Clever Tools to your account, get its information and manage some options. Others are developed in dedicated pages:

- [Network Groups](/docs/ng.md)
- [Applications: configuration](/docs/applications-config.md)
- [Applications: management](/docs/applications-management.md)
- [Applications: deployment and lifecycle](/docs/applications-deployment-lifecycle.md)
Expand Down
Loading
Loading