in controlplane/src/core/bufservices/contract/createContract.ts [21:233]
export function createContract(
opts: RouterOptions,
req: CreateContractRequest,
ctx: HandlerContext,
): Promise<PlainMessage<CreateContractResponse>> {
let logger = getLogger(ctx, opts.logger);
return handleError<PlainMessage<CreateContractResponse>>(ctx, logger, async () => {
req.namespace = req.namespace || DefaultNamespace;
const authContext = await opts.authenticator.authenticate(ctx.requestHeader);
logger = enrichLogger(ctx, logger, authContext);
return opts.db.transaction(async (tx) => {
const orgRepo = new OrganizationRepository(logger, tx, opts.billingDefaultPlanId);
const fedGraphRepo = new FederatedGraphRepository(logger, tx, authContext.organizationId);
const auditLogRepo = new AuditLogRepository(tx);
const namespaceRepo = new NamespaceRepository(tx, authContext.organizationId);
const contractRepo = new ContractRepository(logger, tx, authContext.organizationId);
if (!authContext.hasWriteAccess) {
throw new PublicError(EnumStatusCode.ERR, `The user doesn't have the permissions to perform this operation`);
}
const namespace = await namespaceRepo.byName(req.namespace);
if (!namespace) {
throw new PublicError(EnumStatusCode.ERR_NOT_FOUND, `Could not find namespace ${req.namespace}`);
}
if (await fedGraphRepo.exists(req.name, req.namespace)) {
throw new PublicError(
EnumStatusCode.ERR_ALREADY_EXISTS,
`A graph '${req.name}' already exists in the namespace`,
);
}
if (!isValidUrl(req.routingUrl)) {
throw new PublicError(EnumStatusCode.ERR, `Routing URL is not a valid URL`);
}
if (req.admissionWebhookUrl && !isValidUrl(req.admissionWebhookUrl)) {
throw new PublicError(EnumStatusCode.ERR, `Admission Webhook URL is not a valid URL`);
}
if (req.includeTags.length > 0 && req.excludeTags.length > 0) {
throw new PublicError(
EnumStatusCode.ERR,
`The "exclude" and "include" options for tags are currently mutually exclusive.` +
` Both options have been provided, but one of the options must be empty or unset.`,
);
}
req.excludeTags = [...new Set(req.excludeTags)];
if (!isValidSchemaTags(req.excludeTags)) {
throw new PublicError(EnumStatusCode.ERR, `Provided exclude tags are invalid`);
}
req.includeTags = [...new Set(req.includeTags)];
if (!isValidSchemaTags(req.includeTags)) {
throw new PublicError(EnumStatusCode.ERR, `Provided include tags are invalid`);
}
if (!isValidGraphName(req.name)) {
return {
response: {
code: EnumStatusCode.ERR_INVALID_NAME,
details: `The name of the contract is invalid. Name should start and end with an alphanumeric character. Only '.', '_', '@', '/', and '-' are allowed as separators in between and must be between 1 and 100 characters in length.`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
const count = await fedGraphRepo.count();
const feature = await orgRepo.getFeature({
organizationId: authContext.organizationId,
featureId: 'federated-graphs',
});
const limit = feature?.limit === -1 ? undefined : feature?.limit;
if (limit && count >= limit) {
throw new PublicError(
EnumStatusCode.ERR_LIMIT_REACHED,
`The organization reached the limit of federated graphs and monographs`,
);
}
const sourceGraph = await fedGraphRepo.byName(req.sourceGraphName, req.namespace);
if (!sourceGraph) {
throw new PublicError(
EnumStatusCode.ERR_NOT_FOUND,
`Could not find source graph ${req.sourceGraphName} in namespace ${req.namespace}`,
);
}
// Ignore composability for monographs
if (sourceGraph.supportsFederation && !sourceGraph.isComposable) {
return {
response: {
code: EnumStatusCode.ERR,
details:
`The source graph "${req.sourceGraphName}" is not currently composable.` +
` A contract can only be created if its respective source graph has composed successfully.`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
if (sourceGraph.contract) {
throw new PublicError(
EnumStatusCode.ERR,
`The source graph ${sourceGraph.name} is already a contract. You cannot create a contract from another contract.`,
);
}
const contractGraph = await fedGraphRepo.create({
name: req.name,
createdBy: authContext.userId,
labelMatchers: sourceGraph.labelMatchers,
routingUrl: req.routingUrl,
readme: req.readme,
namespace: req.namespace,
namespaceId: namespace.id,
admissionWebhookURL: req.admissionWebhookUrl,
admissionWebhookSecret: req.admissionWebhookSecret,
supportsFederation: sourceGraph.supportsFederation,
});
const contract = await contractRepo.create({
sourceFederatedGraphId: sourceGraph.id,
downstreamFederatedGraphId: contractGraph.id,
excludeTags: req.excludeTags,
includeTags: req.includeTags,
actorId: authContext.userId,
});
await fedGraphRepo.createGraphCryptoKeyPairs({
federatedGraphId: contractGraph.id,
organizationId: authContext.organizationId,
});
await auditLogRepo.addAuditLog({
organizationId: authContext.organizationId,
auditAction: 'federated_graph.created',
action: 'created',
actorId: authContext.userId,
auditableType: 'federated_graph',
auditableDisplayName: contractGraph.name,
actorDisplayName: authContext.userDisplayName,
apiKeyName: authContext.apiKeyName,
actorType: authContext.auth === 'api_key' ? 'api_key' : 'user',
targetNamespaceId: contractGraph.namespaceId,
targetNamespaceDisplayName: contractGraph.namespace,
});
const compositionErrors: PlainMessage<CompositionError>[] = [];
const deploymentErrors: PlainMessage<DeploymentError>[] = [];
const compositionWarnings: PlainMessage<CompositionWarning>[] = [];
const composition = await fedGraphRepo.composeAndDeployGraphs({
federatedGraphs: [{ ...contractGraph, contract }],
actorId: authContext.userId,
blobStorage: opts.blobStorage,
admissionConfig: {
cdnBaseUrl: opts.cdnBaseUrl,
webhookJWTSecret: opts.admissionWebhookJWTSecret,
},
chClient: opts.chClient!,
});
compositionErrors.push(...composition.compositionErrors);
deploymentErrors.push(...composition.deploymentErrors);
compositionWarnings.push(...composition.compositionWarnings);
if (compositionErrors.length > 0) {
return {
response: {
code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED,
},
compositionErrors,
compositionWarnings,
deploymentErrors: [],
};
}
if (deploymentErrors.length > 0) {
return {
response: {
code: EnumStatusCode.ERR_DEPLOYMENT_FAILED,
},
compositionErrors: [],
compositionWarnings,
deploymentErrors,
};
}
return {
response: {
code: EnumStatusCode.OK,
},
compositionErrors,
compositionWarnings,
deploymentErrors,
};
});
});
}