Skip to content

Commit

Permalink
feat: monograph support (wundergraph#623)
Browse files Browse the repository at this point in the history
Co-authored-by: Dustin Deus <deusdustin@gmail.com>
  • Loading branch information
thisisnithin and StarpTech authored Mar 20, 2024
1 parent 3317c07 commit a255f74
Show file tree
Hide file tree
Showing 93 changed files with 8,106 additions and 1,228 deletions.
30 changes: 0 additions & 30 deletions cli/src/commands/federated-graph/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { endOfDay, formatISO, startOfDay, subDays } from 'date-fns';
import pc from 'picocolors';
import { join } from 'pathe';
import { baseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { CommonGraphCommandOptions } from '../../../core/types/types.js';

type OutputFile = {
createdAt: string;
Expand All @@ -19,11 +19,13 @@ type OutputFile = {
}[];
}[];

export default (opts: BaseCommandOptions) => {
export default (opts: CommonGraphCommandOptions) => {
const graphType = opts.isMonograph ? 'monograph' : 'federated graph';

const command = new Command('changelog');
command.description('Fetches the changelog for a federated graph');
command.argument('<name>', 'The name of the federated graph to update.');
command.option('-n, --namespace [string]', 'The namespace of the federated graph.');
command.description(`Fetches the changelog for a ${graphType}`);
command.argument('<name>', `The name of the ${graphType} to update.`);
command.option('-n, --namespace [string]', `The namespace of the ${graphType}.`);
command.option('-l, --limit [number]', 'Limit of entries. Defaults to 10', '10');
command.option('-f, --offset [number]', 'Offset of entries. Defaults to 0', '0');
command.option('-s, --start [date]', 'Start date. Defaults to 3 days back');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { Command } from 'commander';
import { join } from 'pathe';
import pc from 'picocolors';
import { baseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import program from '../../index.js';
import { CommonGraphCommandOptions } from '../../../core/types/types.js';

export default (opts: CommonGraphCommandOptions) => {
const graphType = opts.isMonograph ? 'monograph' : 'federated graph';

export default (opts: BaseCommandOptions) => {
const command = new Command('fetch');
command.description('Fetches the latest valid SDL of a federated graph. The output can be piped to a file.');
command.argument('<name>', 'The name of the federated graph to fetch.');
command.option('-n, --namespace [string]', 'The namespace of the federated graph.');
command.description(`Fetches the latest valid SDL of a ${graphType}. The output can be piped to a file.`);
command.argument('<name>', `The name of the ${graphType} to fetch.`);
command.option('-n, --namespace [string]', `The namespace of the ${graphType}.`);
command.option('-o, --out [string]', 'Destination file for the SDL.');
command.action(async (name, options) => {
const resp = await opts.client.platform.getFederatedGraphSDLByName(
Expand All @@ -24,13 +25,9 @@ export default (opts: BaseCommandOptions) => {
},
);

if (resp.response?.code === EnumStatusCode.ERR_FREE_TRIAL_EXPIRED) {
program.error(resp.response.details || 'Free trial has concluded. Please talk to sales to upgrade your plan.');
}

if (resp.response?.code === EnumStatusCode.ERR_NOT_FOUND) {
console.log(`${pc.red(`No valid composition could be fetched for federated graph ${pc.bold(name)}`)}`);
console.log('Please check the name and the composition status of the federated graph in the Studio.');
console.log(`${pc.red(`No valid composition could be fetched for ${graphType} ${pc.bold(name)}`)}`);
console.log(`Please check the name and the composition status of the ${graphType} in the Studio.`);
if (resp.response?.details) {
console.log(pc.red(pc.bold(resp.response?.details)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import logSymbols from 'log-symbols';
import pc from 'picocolors';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { joinLabel } from '@wundergraph/cosmo-shared';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { baseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';
import { baseHeaders } from '../../../../core/config.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('check');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { Command, program } from 'commander';
import { resolve } from 'pathe';
import pc from 'picocolors';
import ora from 'ora';
import { baseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { baseHeaders } from '../../../../core/config.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('create');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import pc from 'picocolors';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import inquirer from 'inquirer';
import ora from 'ora';
import { baseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { baseHeaders } from '../../../../core/config.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('delete');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb
import Table from 'cli-table3';
import logSymbols from 'log-symbols';
import { join } from 'pathe';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { baseHeaders } from '../../../core/config.js';
import program from '../../index.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';
import { baseHeaders } from '../../../../core/config.js';
import program from '../../../index.js';

type OutputFile = {
name: string;
Expand All @@ -32,6 +32,7 @@ export default (opts: BaseCommandOptions) => {
limit: 0,
offset: 0,
namespace: options.namespace,
supportsFederation: true,
},
{
headers: baseHeaders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import CliTable3 from 'cli-table3';
import { Command, program } from 'commander';
import pc from 'picocolors';
import ora from 'ora';
import { baseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { baseHeaders } from '../../../../core/config.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('move');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import pc from 'picocolors';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { resolve } from 'pathe';
import ora from 'ora';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { baseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';
import { baseHeaders } from '../../../../core/config.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('update');
Expand All @@ -20,7 +20,7 @@ export default (opts: BaseCommandOptions) => {
);
command.option(
'--label-matcher [labels...]',
'The label matcher is used to select the subgraphs to federate. The labels are passed in the format <key>=<value> <key>=<value>. They are separated by spaces and grouped using comma. Example: --label-matcher team=A,team=B env=prod',
'The label matcher is used to select the subgraphs to federate. The labels are passed in the format <key>=<value> <key>=<value>. They are separated by spaces and grouped using comma. Example: --label-matcher team=A,team=B env=prod. This will overwrite existing label matchers.',
);
command.option(
'--unset-label-matchers',
Expand Down
30 changes: 30 additions & 0 deletions cli/src/commands/graph/federated-graph/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Command } from 'commander';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { checkAPIKey } from '../../../utils.js';
import FetchFederatedGraphCommand from '../common/fetch.js';
import GetFederatedGraphChangelog from '../common/changelog.js';
import ListFederatedGraphs from './commands/list.js';
import CheckFederatedGraphCommand from './commands/check.js';
import CreateFederatedGraphCommand from './commands/create.js';
import DeleteFederatedGraphCommand from './commands/delete.js';
import UpdateFederatedGraphCommand from './commands/update.js';
import MoveFederatedGraph from './commands/move.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('federated-graph');
command.description('Provides commands for creating and managing a federated graph');
command.addCommand(CreateFederatedGraphCommand(opts));
command.addCommand(FetchFederatedGraphCommand(opts));
command.addCommand(DeleteFederatedGraphCommand(opts));
command.addCommand(UpdateFederatedGraphCommand(opts));
command.addCommand(CheckFederatedGraphCommand(opts));
command.addCommand(ListFederatedGraphs(opts));
command.addCommand(GetFederatedGraphChangelog(opts));
command.addCommand(MoveFederatedGraph(opts));

command.hook('preAction', () => {
checkAPIKey();
});

return command;
};
77 changes: 77 additions & 0 deletions cli/src/commands/graph/monograph/commands/check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { PartialMessage } from '@bufbuild/protobuf';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { GitInfo } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { Command, program } from 'commander';
import { resolve } from 'pathe';
import pc from 'picocolors';
import { baseHeaders, config } from '../../../../core/config.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';
import { verifyGitHubIntegration } from '../../../../github.js';
import { handleCheckResult } from '../../../../handle-check-result.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('check');
command.description('Checks for breaking changes and errors.');
command.argument('<name>', 'The name of the monograph on which the check operation is to be performed.');
command.option('-n, --namespace [string]', 'The namespace of the monograph.');
command.option('--schema <path-to-schema>', 'The path of the new schema file.');

command.action(async (name, options) => {
const schemaFile = resolve(process.cwd(), options.schema);

if (!existsSync(schemaFile)) {
console.log(
pc.red(
pc.bold(`The schema file '${pc.bold(schemaFile)}' does not exist. Please check the path and try again.`),
),
);
return;
}

const { gitInfo, ignoreErrorsDueToGitHubIntegration } = await verifyGitHubIntegration(opts.client);

const graphResp = await opts.client.platform.getFederatedGraphByName(
{
name,
namespace: options.namespace,
includeMetrics: false,
},
{
headers: baseHeaders,
},
);

if (graphResp.response?.code !== EnumStatusCode.OK) {
program.error(pc.red(`Could not perform check. ${graphResp.response?.details}`));
}

if (graphResp.subgraphs.length === 0) {
program.error(pc.red(`Could not perform check. No subgraph found.`));
}

const subgraph = graphResp.subgraphs[0];

const resp = await opts.client.platform.checkSubgraphSchema(
{
subgraphName: subgraph.name,
namespace: subgraph.namespace,
schema: await readFile(schemaFile),
gitInfo,
delete: false,
},
{
headers: baseHeaders,
},
);

const success = handleCheckResult(resp);

if (!success && !ignoreErrorsDueToGitHubIntegration) {
process.exit(1);
}
});

return command;
};
78 changes: 78 additions & 0 deletions cli/src/commands/graph/monograph/commands/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { readFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { Command, program } from 'commander';
import { resolve } from 'pathe';
import pc from 'picocolors';
import { parseGraphQLSubscriptionProtocol } from '@wundergraph/cosmo-shared';
import ora from 'ora';
import { baseHeaders } from '../../../../core/config.js';
import { BaseCommandOptions } from '../../../../core/types/types.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('create');
command.description('Creates a monograph on the control plane.');
command.argument('<name>', 'The name of the graph to create. It is used to uniquely identify your graph.');
command.option('-n, --namespace [string]', 'The namespace of the graph.');
command.requiredOption(
'-r, --routing-url <url>',
'The routing url of your router. This is the url that the router will be accessible at.',
);
command.requiredOption('-u, --graph-url <url>', 'The url of your GraphQL server that is accessible from the router.');
command.option('--subscription-url [url]', 'The url used for subscriptions. If empty, it defaults to graph url.');
command.option(
'--subscription-protocol <protocol>',
'The protocol to use when subscribing to the graph. The supported protocols are ws, sse, and sse_post.',
);
command.option(
'--admission-webhook-url <url>',
'The admission webhook url. This is the url that the controlplane will use to implement admission control for the monograph. This is optional.',
[],
);
command.option('--readme <path-to-readme>', 'The markdown file which describes the graph.');
command.action(async (name, options) => {
let readmeFile;
if (options.readme) {
readmeFile = resolve(process.cwd(), options.readme);
if (!existsSync(readmeFile)) {
program.error(
pc.red(
pc.bold(`The readme file '${pc.bold(readmeFile)}' does not exist. Please check the path and try again.`),
),
);
}
}

const spinner = ora('Federated Graph is being created...').start();

const resp = await opts.client.platform.createMonograph(
{
name,
namespace: options.namespace,
routingUrl: options.routingUrl,
readme: readmeFile ? await readFile(readmeFile, 'utf8') : undefined,
graphUrl: options.graphUrl,
subscriptionUrl: options.subscriptionUrl === true ? '' : options.subscriptionUrl,
subscriptionProtocol: options.subscriptionProtocol
? parseGraphQLSubscriptionProtocol(options.subscriptionProtocol)
: undefined,
admissionWebhookURL: options.admissionWebhookUrl,
},
{
headers: baseHeaders,
},
);

if (resp.response?.code === EnumStatusCode.OK) {
spinner.succeed('Monograph was created successfully.');
} else {
spinner.fail(`Failed to create monograph.`);
if (resp.response?.details) {
console.log(pc.red(pc.bold(resp.response?.details)));
}
process.exit(1);
}
});

return command;
};
Loading

0 comments on commit a255f74

Please sign in to comment.