Skip to content

Commit

Permalink
feat(domain): introduce domain overview command
Browse files Browse the repository at this point in the history
  • Loading branch information
hsablonniere committed Oct 16, 2024
1 parent a485073 commit bb1905b
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 4 deletions.
19 changes: 16 additions & 3 deletions bin/clever.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ function run () {
metavar: 'TEXT',
description: 'Check only domains containing the provided text',
}),
domainOverviewFilter: cliparse.option('filter', {
aliases: ['f'],
default: '',
metavar: 'TEXT',
description: 'Get only domains containing the provided text',
}),
naturalName: cliparse.flag('natural-name', {
aliases: ['n'],
description: 'Show the application names or aliases if possible',
Expand Down Expand Up @@ -583,10 +589,12 @@ function run () {
const domainCreateCommand = cliparse.command('add', {
description: 'Add a domain name to an application',
args: [args.fqdn],
options: [opts.alias, opts.appIdOrName],
}, domain.add);
const domainRemoveCommand = cliparse.command('rm', {
description: 'Remove a domain name from an application',
args: [args.fqdn],
options: [opts.alias, opts.appIdOrName],
}, domain.rm);
const domainSetFavouriteCommand = cliparse.command('set', {
description: 'Set the favourite domain for an application',
Expand All @@ -597,16 +605,21 @@ function run () {
}, domain.unsetFavourite);
const domainFavouriteCommands = cliparse.command('favourite', {
description: 'Manage the favourite domain name for an application',
options: [opts.alias, opts.appIdOrName],
commands: [domainSetFavouriteCommand, domainUnsetFavouriteCommand],
}, domain.getFavourite);
const domainDiagApplicationCommand = cliparse.command('diag', {
description: 'Check if domains associated to a specific app are properly configured',
options: [opts.humanJsonOutputFormat, opts.domain],
options: [opts.alias, opts.appIdOrName, opts.humanJsonOutputFormat, opts.domain],
}, domain.diagApplication);
const domainOverviewCommand = cliparse.command('overview', {
description: 'Get an overview of all your domains (all orgas, all apps)',
options: [opts.humanJsonOutputFormat, opts.domainOverviewFilter],
}, domain.overview);
const domainCommands = cliparse.command('domain', {
description: 'Manage domain names for an application',
options: [opts.alias, opts.appIdOrName],
commands: [domainCreateCommand, domainFavouriteCommands, domainRemoveCommand, domainDiagApplicationCommand],
privateOptions: [opts.alias, opts.appIdOrName],
commands: [domainCreateCommand, domainFavouriteCommands, domainRemoveCommand, domainDiagApplicationCommand, domainOverviewCommand],
}, domain.list);

// DRAIN COMMANDS
Expand Down
127 changes: 126 additions & 1 deletion src/commands/domain.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import * as Application from '../models/application.js';
import { Logger } from '../logger.js';
import { get as getApp, addDomain, getFavouriteDomain as getFavouriteDomainWithError, markFavouriteDomain, unmarkFavouriteDomain, removeDomain } from '@clevercloud/client/esm/api/v2/application.js';
import {
get as getApp,
addDomain,
getFavouriteDomain as getFavouriteDomainWithError,
markFavouriteDomain,
unmarkFavouriteDomain,
removeDomain,
getAllDomains,
} from '@clevercloud/client/esm/api/v2/application.js';
import { getSummary } from '@clevercloud/client/cjs/api/v2/user.js';
import { sendToApi } from '../models/send-to-api.js';
import colors from 'colors/safe.js';
import { parse as parseDomain } from 'tldts';
import { diagDomainConfig } from '@clevercloud/client/esm/utils/diag-domain-config.js';
import { DnsResolver } from '../models/node-dns-resolver.js';
import _ from 'lodash';

/**
* @typedef {import('@clevercloud/client/esm/utils/diag-domain-config.types.js').DomainInfo} DomainInfo
Expand Down Expand Up @@ -302,3 +312,118 @@ function sortDomains (parsedDomains) {
function printlnWithIndent (text, indentLevel) {
Logger.println(' '.repeat(indentLevel) + text);
}

export async function overview (params) {

const { format, filter } = params.options;

const summary = await getSummary().then(sendToApi);
const consoleUrl = summary.user.partnerConsoleUrl;

const applications = [
...summary.user.applications.map((app) => {
return { ownerName: summary.user.name, ownerId: summary.user.id, ...app };
}),
...summary.organisations.flatMap((o) => {
return o.applications.map((app) => {
return { ownerName: o.name, ownerId: o.id, ...app };
});
}),
];

const applicationsWithDomains = await Promise.all(
applications.map(async (app) => {
const domains = await getAllDomains({ id: app.ownerId, appId: app.id }).then(sendToApi);
return { app, domains };
}),
);

const applicationsWithParsedDomain = applicationsWithDomains
.flatMap(({ app, domains }) => {
return domains
.filter((domain) => filter == null || domain.fqdn.includes(filter))
.map((domain) => {

const parsedDomain = parseDomain(domain.fqdn);
const pathname = new URL('https://' + domain.fqdn).pathname;
const subdomains = parsedDomain.subdomain !== '' ? parsedDomain.subdomain.split('.') : [];

// We're trying to create an propertyPath for lodash to create a tree structure object,
// the propertyPath for `aaa.bbb.ccc.example.com/the-path` would be:
// ["example.com", "example.com.ccc", "example.com.ccc.bbb", "example.com.ccc.bbb.aaa", "/path-aaa"]",

const sortSegments = [parsedDomain.domain, ...subdomains.reverse()];
const propetyPath = sortSegments.map((item, i, all) => {
return all.slice(0, i + 1).reverse().join('.');
});
propetyPath.push(pathname);

return {
ownerId: app.ownerId,
ownerName: app.ownerName,
appId: app.id,
appName: app.name,
appConsoleUrl: `${consoleUrl}/${app.id}`,
appVariantSlug: app.variantSlug,
domain: domain.fqdn,
propetyPath,
};
});
});

const applicationsWithParsedDomainAsTree = {};
for (const { propetyPath, ...appWithDomain } of applicationsWithParsedDomain) {
_.set(applicationsWithParsedDomainAsTree, propetyPath, appWithDomain);
}

const applicationsWithParsedDomainAsSortedTree = recursiveSort(applicationsWithParsedDomainAsTree);

switch (format) {
case 'json':
Logger.printJson(applicationsWithParsedDomainAsSortedTree);
break;
case 'human':
default:
if (Object.keys(applicationsWithParsedDomainAsSortedTree).length === 0) {
Logger.println(`No matches for filter "${filter}"`);
}
else {
recursiveDisplay(applicationsWithParsedDomainAsSortedTree);
}
break;
}
}

function recursiveSort (obj) {

if (typeof obj === 'object' && obj.appId != null) {
return obj;
}

const sortedObj = {};
Object.keys(obj).sort((a, b) => a.localeCompare(b)).forEach((key) => {
sortedObj[key] = recursiveSort(obj[key]);
});

return sortedObj;
}

function recursiveDisplay (obj, indentLevel = 0, isLast) {

if (typeof obj === 'object' && obj.appId != null) {
Logger.println(' '.repeat(indentLevel) + `${obj.ownerName} / ${obj.appName} / ${obj.appVariantSlug}`);
Logger.println(' '.repeat(indentLevel) + colors.blue(obj.appConsoleUrl));
return;
}

for (const [propertyPath, subObj] of Object.entries(obj)) {
if (propertyPath !== '/') {
Logger.println('');
Logger.println(' '.repeat(indentLevel) + colors.yellow(propertyPath));
recursiveDisplay(subObj, indentLevel + 2);
}
else {
recursiveDisplay(subObj, indentLevel);
}
}
}

0 comments on commit bb1905b

Please sign in to comment.