generator-begcode
Version:
Spring Boot + Angular/React/Vue in one handy generator
433 lines (432 loc) • 23.6 kB
JavaScript
import { existsSync } from 'fs';
import pathjs from 'path';
import chalk from 'chalk';
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
import normalize from 'normalize-path';
import { defaults } from 'lodash-es';
import BaseWorkspacesGenerator from '../base-workspaces/index.js';
import { deploymentOptions, monitoringTypes, serviceDiscoveryTypes } from '../../lib/jhipster/index.js';
import { GENERATOR_BOOTSTRAP_WORKSPACES } from '../generator-list.js';
import { convertSecretToBase64, createBase64Secret, createFaker, stringHashCode } from '../base/support/index.js';
import { checkDocker } from '../base-workspaces/internal/docker-base.js';
import { loadDockerDependenciesTask } from '../base-workspaces/internal/index.js';
import { loadDerivedPlatformConfig, loadPlatformConfig } from '../server/support/index.js';
import command from './command.js';
import { writeFiles } from './files.js';
const { PROMETHEUS, NO: NO_MONITORING } = monitoringTypes;
const { CONSUL, EUREKA, NO: NO_SERVICE_DISCOVERY } = serviceDiscoveryTypes;
const { Options: DeploymentOptions } = deploymentOptions;
export default class DockerComposeGenerator extends BaseWorkspacesGenerator {
existingDeployment;
jwtSecretKey;
async beforeQueue() {
this.parseJHipsterArguments(command.arguments);
if (this.appsFolders && this.appsFolders.length > 0) {
this.jhipsterConfig.appsFolders = this.appsFolders;
}
await this.dependsOnJHipster(GENERATOR_BOOTSTRAP_WORKSPACES);
if (!this.fromBlueprint) {
await this.composeWithBlueprints();
}
}
get initializing() {
return this.asInitializingTaskGroup({
sayHello() {
this.log.log(chalk.white(`${chalk.bold('🐳')} Welcome to the JHipster Docker Compose Sub-Generator ${chalk.bold('🐳')}`));
this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`));
},
checkDocker,
async checkDockerCompose({ control }) {
if (this.skipChecks)
return;
if (!control.enviromentHasDockerCompose) {
throw new Error(`Docker Compose V2 is not installed on your computer.
Read https://docs.docker.com/compose/install/
`);
}
},
});
}
get [BaseWorkspacesGenerator.INITIALIZING]() {
return this.delegateTasksToBlueprint(() => this.initializing);
}
get loading() {
return this.asLoadingTaskGroup({
loadWorkspacesConfig() {
this.loadWorkspacesConfig();
},
});
}
get [BaseWorkspacesGenerator.LOADING]() {
return this.delegateTasksToBlueprint(() => this.loading);
}
get promptingWorkspaces() {
return this.asAnyTaskGroup({
async askForMonitoring({ workspaces }) {
if (workspaces.existingWorkspaces && !this.options.askAnswered)
return;
await this.askForMonitoring();
},
async askForClustersMode({ workspaces, applications }) {
if (workspaces.existingWorkspaces && !this.options.askAnswered)
return;
await this.askForClustersMode({ applications });
},
async askForServiceDiscovery({ workspaces, applications }) {
if (workspaces.existingWorkspaces && !this.options.askAnswered)
return;
await this.askForServiceDiscovery({ applications });
},
});
}
get [BaseWorkspacesGenerator.PROMPTING_WORKSPACES]() {
return this.delegateTasksToBlueprint(() => this.promptingWorkspaces);
}
get configuringWorkspaces() {
return this.asAnyTaskGroup({
configureBaseDeployment({ applications }) {
this.jhipsterConfig.jwtSecretKey =
this.jhipsterConfig.jwtSecretKey ?? this.jwtSecretKey ?? createBase64Secret(this.options.reproducibleTests);
if (applications.some(app => app.serviceDiscoveryEureka)) {
this.jhipsterConfig.adminPassword = this.jhipsterConfig.adminPassword ?? 'admin';
}
},
});
}
get [BaseWorkspacesGenerator.CONFIGURING_WORKSPACES]() {
return this.delegateTasksToBlueprint(() => this.configuringWorkspaces);
}
get loadingWorkspaces() {
return this.asAnyTaskGroup({
async loadBaseDeployment({ deployment }) {
deployment.jwtSecretKey = this.jhipsterConfig.jwtSecretKey;
await loadDockerDependenciesTask.call(this, { context: deployment });
},
loadPlatformConfig({ deployment }) {
this.loadDeploymentConfig({ deployment });
},
});
}
get [BaseWorkspacesGenerator.LOADING_WORKSPACES]() {
return this.delegateTasksToBlueprint(() => this.loadingWorkspaces);
}
get preparingWorkspaces() {
return this.asAnyTaskGroup({
prepareDeployment({ deployment, applications }) {
this.prepareDeploymentDerivedProperties({ deployment, applications });
},
});
}
get [BaseWorkspacesGenerator.PREPARING_WORKSPACES]() {
return this.delegateTasksToBlueprint(() => this.preparingWorkspaces);
}
get default() {
return this.asAnyTaskGroup({
async setAppsYaml({ workspaces, deployment, applications }) {
const faker = await createFaker();
deployment.keycloakRedirectUris = '';
deployment.appsYaml = applications.map(appConfig => {
const lowercaseBaseName = appConfig.baseName.toLowerCase();
appConfig.clusteredDb = deployment.clusteredDbApps?.includes(appConfig.appFolder);
const parentConfiguration = {};
const path = this.destinationPath(workspaces.directoryPath, appConfig.appFolder);
const yaml = parseYaml(this.fs.read(`${path}/src/main/docker/app.yml`));
const yamlConfig = yaml.services.app;
if (yamlConfig.depends_on) {
yamlConfig.depends_on = Object.fromEntries(Object.entries(yamlConfig.depends_on).map(([serviceName, config]) => {
if (['keycloak', 'jhipster-registry', 'consul'].includes(serviceName)) {
return [serviceName, config];
}
return [`${lowercaseBaseName}-${serviceName}`, config];
}));
}
if (appConfig.applicationTypeGateway || appConfig.applicationTypeMonolith) {
if (deployment.keycloakSecrets === undefined && appConfig.authenticationTypeOauth2) {
faker.seed(stringHashCode(appConfig.baseName));
deployment.keycloakSecrets = Array.from(Array(6), () => faker.string.uuid());
}
deployment.keycloakRedirectUris += `"http://localhost:${appConfig.composePort}/*", "https://localhost:${appConfig.composePort}/*", `;
if (appConfig.devServerPort !== undefined) {
deployment.keycloakRedirectUris += `"http://localhost:${appConfig.devServerPort}/*", `;
}
if (appConfig.devServerPortProxy !== undefined) {
deployment.keycloakRedirectUris += `"http://localhost:${appConfig.devServerPortProxy}/*", `;
}
const ports = yamlConfig.ports[0].split(':').slice(-2);
ports[0] = appConfig.composePort;
yamlConfig.ports[0] = ports.join(':');
}
if (yamlConfig.environment) {
yamlConfig.environment = yamlConfig.environment.map(envOption => {
[
'SPRING_R2DBC_URL',
'SPRING_DATASOURCE_URL',
'SPRING_LIQUIBASE_URL',
'SPRING_NEO4J_URI',
'SPRING_DATA_MONGODB_URI',
'JHIPSTER_CACHE_REDIS_SERVER',
'SPRING_ELASTICSEARCH_URIS',
].forEach(varName => {
if (envOption.startsWith(varName)) {
envOption = envOption
.replace('://', `://${lowercaseBaseName}-`)
.replace('oracle:thin:@', `oracle:thin:@${lowercaseBaseName}-`);
}
});
['JHIPSTER_CACHE_MEMCACHED_SERVERS', 'SPRING_COUCHBASE_CONNECTION_STRING', 'SPRING_CASSANDRA_CONTACTPOINTS'].forEach(varName => {
if (envOption.startsWith(varName)) {
envOption = envOption.replace(`${varName}=`, `${varName}=${lowercaseBaseName}-`);
}
});
return envOption;
});
}
if (appConfig.applicationTypeMonolith && deployment.monitoring === PROMETHEUS) {
yamlConfig.environment.push('JHIPSTER_LOGGING_LOGSTASH_ENABLED=false');
yamlConfig.environment.push('MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true');
}
if (deployment.serviceDiscoveryType === EUREKA) {
yamlConfig.environment.push(`JHIPSTER_REGISTRY_PASSWORD=${deployment.adminPassword}`);
}
const hasNoServiceDiscovery = !deployment.serviceDiscoveryType && deployment.serviceDiscoveryType !== NO_SERVICE_DISCOVERY;
if (hasNoServiceDiscovery && appConfig.skipClient) {
yamlConfig.environment.push('SERVER_PORT=80');
}
parentConfiguration[lowercaseBaseName] = yamlConfig;
if (appConfig.databaseTypeAny && !appConfig.prodDatabaseTypeOracle) {
const database = appConfig.databaseTypeSql ? appConfig.prodDatabaseType : appConfig.databaseType;
const relativePath = normalize(pathjs.relative(this.destinationRoot(), `${path}/src/main/docker`));
const databaseYaml = parseYaml(this.fs.read(`${path}/src/main/docker/${database}.yml`));
const databaseServiceName = `${lowercaseBaseName}-${database}`;
let databaseYamlConfig = databaseYaml.services[database];
delete databaseYamlConfig.ports;
if (appConfig.databaseTypeCassandra) {
const cassandraMigrationYaml = parseYaml(this.fs.read(`${path}/src/main/docker/cassandra-migration.yml`));
const cassandraMigrationConfig = cassandraMigrationYaml.services[`${database}-migration`];
cassandraMigrationConfig.build.context = relativePath;
const cqlFilesRelativePath = normalize(pathjs.relative(this.destinationRoot(), `${path}/src/main/resources/config/cql`));
cassandraMigrationConfig.volumes[0] = `${cqlFilesRelativePath}:/cql:ro`;
parentConfiguration[`${databaseServiceName}-migration`] = cassandraMigrationConfig;
}
if (appConfig.databaseTypeCouchbase) {
databaseYamlConfig.build.context = relativePath;
}
if (appConfig.clusteredDb) {
const clusterDbYaml = parseYaml(this.fs.read(`${path}/src/main/docker/${database}-cluster.yml`));
const dbNodeConfig = clusterDbYaml.services[`${database}-node`];
dbNodeConfig.build.context = relativePath;
databaseYamlConfig = clusterDbYaml.services[database];
delete databaseYamlConfig.ports;
if (appConfig.databaseTypeCouchbase) {
databaseYamlConfig.build.context = relativePath;
}
parentConfiguration[`${databaseServiceName}-node`] = dbNodeConfig;
if (appConfig.databaseTypeMongodb) {
parentConfiguration[`${databaseServiceName}-config`] = clusterDbYaml.services[`${database}-config`];
}
}
parentConfiguration[databaseServiceName] = databaseYamlConfig;
}
if (appConfig.searchEngineElasticsearch) {
const searchEngine = appConfig.searchEngine;
const searchEngineYaml = parseYaml(this.fs.read(`${path}/src/main/docker/${searchEngine}.yml`));
const searchEngineConfig = searchEngineYaml.services[searchEngine];
delete searchEngineConfig.ports;
parentConfiguration[`${lowercaseBaseName}-${searchEngine}`] = searchEngineConfig;
}
if (appConfig.cacheProviderMemcached) {
const memcachedYaml = parseYaml(this.readDestination(`${path}/src/main/docker/memcached.yml`).toString());
const memcachedConfig = memcachedYaml.services.memcached;
delete memcachedConfig.ports;
parentConfiguration[`${lowercaseBaseName}-memcached`] = memcachedConfig;
}
if (appConfig.cacheProviderRedis) {
const redisYaml = parseYaml(this.readDestination(`${path}/src/main/docker/redis.yml`).toString());
const redisConfig = redisYaml.services.redis;
delete redisConfig.ports;
parentConfiguration[`${lowercaseBaseName}-redis`] = redisConfig;
}
deployment.authenticationType = appConfig.authenticationType;
let yamlString = stringifyYaml(parentConfiguration, { indent: 2, lineWidth: 0 });
const yamlArray = yamlString.split('\n');
for (let j = 0; j < yamlArray.length; j++) {
yamlArray[j] = ` ${yamlArray[j]}`;
}
yamlString = yamlArray.join('\n');
return yamlString;
});
},
});
}
get [BaseWorkspacesGenerator.DEFAULT]() {
return this.delegateTasksToBlueprint(() => this.default);
}
get writing() {
return writeFiles();
}
get [BaseWorkspacesGenerator.WRITING]() {
return this.delegateTasksToBlueprint(() => this.writing);
}
get end() {
return this.asAnyTaskGroup({
end({ workspaces, applications }) {
this.checkApplicationsDockerImages({ workspaces, applications });
this.log.verboseInfo(`You can launch all your infrastructure by running : ${chalk.cyan('docker compose up -d')}`);
const uiApplications = applications.filter(app => (app.applicationTypeGateway || app.applicationTypeMonolith) && app.clientFrameworkAny);
if (uiApplications.length > 0) {
this.log.log('\nYour applications will be accessible on these URLs:');
for (const application of uiApplications) {
this.log.verboseInfo(`\t- ${application.baseName}: http://localhost:${application.composePort}`);
}
this.log.log('\n');
}
},
});
}
get [BaseWorkspacesGenerator.END]() {
return this.delegateTasksToBlueprint(() => this.end);
}
checkApplicationsDockerImages({ workspaces, applications }) {
this.log.log('\nChecking Docker images in applications directories...');
let imagePath = '';
let runCommand = '';
let hasWarning = false;
let warningMessage = 'To generate the missing Docker image(s), please run:\n';
applications.forEach(application => {
if (application.buildToolGradle) {
imagePath = this.destinationPath(workspaces.directoryPath, application.appFolder, 'build/jib-cache');
runCommand = `./gradlew bootJar -Pprod jibDockerBuild${process.arch === 'arm64' ? ' -PjibArchitecture=arm64' : ''}`;
}
else if (application.buildToolMaven) {
imagePath = this.destinationPath(workspaces.directoryPath, application.appFolder, '/target/jib-cache');
runCommand = `./mvnw -ntp -Pprod verify jib:dockerBuild${process.arch === 'arm64' ? ' -Djib-maven-plugin.architecture=arm64' : ''}`;
}
if (!existsSync(imagePath)) {
hasWarning = true;
warningMessage += ` ${chalk.cyan(runCommand)} in ${this.destinationPath(workspaces.directoryPath, application.appFolder)}\n`;
}
});
if (hasWarning) {
this.log.warn('Docker Compose configuration generated, but no Jib cache found');
this.log.warn('If you forgot to generate the Docker image for this application, please run:');
this.log.log(chalk.red(warningMessage));
}
else {
this.log.verboseInfo(`${chalk.bold.green('Docker Compose configuration successfully generated!')}`);
}
}
get deploymentConfigWithDefaults() {
return defaults({}, this.jhipsterConfig, DeploymentOptions.defaults(this.jhipsterConfig.deploymentType));
}
loadDeploymentConfig({ deployment }) {
const config = this.deploymentConfigWithDefaults;
deployment.clusteredDbApps = config.clusteredDbApps;
deployment.adminPassword = config.adminPassword;
deployment.jwtSecretKey = config.jwtSecretKey;
loadPlatformConfig({ config, application: deployment });
loadDerivedPlatformConfig({ application: deployment });
}
prepareDeploymentDerivedProperties({ deployment, applications }) {
if (deployment.adminPassword) {
deployment.adminPasswordBase64 = convertSecretToBase64(deployment.adminPassword);
}
deployment.usesOauth2 = applications.some(appConfig => appConfig.authenticationTypeOauth2);
deployment.useKafka = applications.some(appConfig => appConfig.messageBrokerKafka);
deployment.usePulsar = applications.some(appConfig => appConfig.messageBrokerPulsar);
deployment.useMemcached = applications.some(appConfig => appConfig.cacheProviderMemcached);
deployment.useRedis = applications.some(appConfig => appConfig.cacheProviderRedis);
deployment.includesApplicationTypeGateway = applications.some(appConfig => appConfig.applicationTypeGateway);
deployment.entryPort = 8080;
deployment.appConfigs = applications;
deployment.applications = applications;
}
async askForMonitoring() {
await this.prompt([
{
type: 'list',
name: 'monitoring',
message: 'Do you want to setup monitoring for your applications ?',
choices: [
{
value: NO_MONITORING,
name: 'No',
},
{
value: PROMETHEUS,
name: 'Yes, for metrics only with Prometheus',
},
],
default: NO_MONITORING,
},
], this.config);
}
async askForClustersMode({ applications }) {
const clusteredDbApps = applications.filter(app => app.databaseTypeMongodb || app.databaseTypeCouchbase).map(app => app.appFolder);
if (clusteredDbApps.length === 0)
return;
await this.prompt([
{
type: 'checkbox',
name: 'clusteredDbApps',
message: 'Which applications do you want to use with clustered databases (only available with MongoDB and Couchbase)?',
choices: clusteredDbApps,
default: clusteredDbApps,
},
], this.config);
}
async askForServiceDiscovery({ applications }) {
const serviceDiscoveryEnabledApps = applications.filter(app => app.serviceDiscoveryAny);
if (serviceDiscoveryEnabledApps.length === 0) {
this.jhipsterConfig.serviceDiscoveryType = NO_SERVICE_DISCOVERY;
return;
}
if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryConsul)) {
this.jhipsterConfig.serviceDiscoveryType = CONSUL;
this.log.log(chalk.green('Consul detected as the service discovery and configuration provider used by your apps'));
}
else if (serviceDiscoveryEnabledApps.every(app => app.serviceDiscoveryEureka)) {
this.jhipsterConfig.serviceDiscoveryType = EUREKA;
this.log.log(chalk.green('JHipster registry detected as the service discovery and configuration provider used by your apps'));
}
else {
this.log.warn(chalk.yellow('Unable to determine the service discovery and configuration provider to use from your apps configuration.'));
this.log.verboseInfo('Your service discovery enabled apps:');
serviceDiscoveryEnabledApps.forEach(app => {
this.log.verboseInfo(` -${app.baseName} (${app.serviceDiscoveryType})`);
});
await this.prompt([
{
type: 'list',
name: 'serviceDiscoveryType',
message: 'Which Service Discovery registry and Configuration server would you like to use ?',
choices: [
{
value: CONSUL,
name: 'Consul',
},
{
value: EUREKA,
name: 'JHipster Registry',
},
{
value: NO_SERVICE_DISCOVERY,
name: 'No Service Discovery and Configuration',
},
],
default: CONSUL,
},
], this.config);
}
if (this.jhipsterConfig.serviceDiscoveryType === EUREKA) {
await this.prompt([
{
type: 'input',
name: 'adminPassword',
message: 'Enter the admin password used to secure the JHipster Registry',
default: 'admin',
validate: input => (input.length < 5 ? 'The password must have at least 5 characters' : true),
},
], this.config);
}
}
}