Skip to content

Commit

Permalink
spring-boot: be verbose about docker compose requirement in default c…
Browse files Browse the repository at this point in the history
…onfig
  • Loading branch information
mshima committed Jan 3, 2025
1 parent 2f96b5f commit 2b84fed
Show file tree
Hide file tree
Showing 16 changed files with 153 additions and 52 deletions.
3 changes: 3 additions & 0 deletions generators/app/__snapshots__/generator.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ exports[`generator - app with default config should match snapshot 1`] = `
"blueprints": [],
"buildTool": "maven",
"buildToolAny": true,
"buildToolExecutable": "mvnw",
"buildToolGradle": false,
"buildToolMaven": true,
"buildToolUnknown": false,
Expand Down Expand Up @@ -909,6 +910,7 @@ exports[`generator - app with gateway should match snapshot 1`] = `
"blueprints": [],
"buildTool": "maven",
"buildToolAny": true,
"buildToolExecutable": "mvnw",
"buildToolGradle": false,
"buildToolMaven": true,
"buildToolUnknown": false,
Expand Down Expand Up @@ -1572,6 +1574,7 @@ exports[`generator - app with microservice should match snapshot 1`] = `
"blueprints": [],
"buildTool": "maven",
"buildToolAny": true,
"buildToolExecutable": "mvnw",
"buildToolGradle": false,
"buildToolMaven": true,
"buildToolUnknown": false,
Expand Down
2 changes: 2 additions & 0 deletions generators/base-application/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ export type CommonClientServerApplication<Entity> = BaseApplication &
SpringBootApplication &
ClientApplication &
ExportApplicationPropertiesFromCommand<typeof import('../git/command.ts').default> &
ExportApplicationPropertiesFromCommand<typeof import('../docker/command.ts').default> &
import('../docker/types.d.ts').DockerApplicationType &
ExportApplicationPropertiesFromCommand<typeof import('../project-name/command.ts').default> &
ApplicationProperties & {
clientRootDir: string;
Expand Down
13 changes: 13 additions & 0 deletions generators/base/shared-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import assert from 'node:assert';
import { existsSync, readFileSync, statSync } from 'fs';
import { rm } from 'fs/promises';
import { isAbsolute, join, relative } from 'path';
import { execaCommandSync } from 'execa';
import { lt as semverLessThan } from 'semver';
import { defaults } from 'lodash-es';
import type { MemFsEditor } from 'mem-fs-editor';
Expand Down Expand Up @@ -136,6 +137,18 @@ export default class SharedData<EntityType extends BaseEntity = Entity, Applicat
},
});

if (this._storage.control.enviromentHasDockerCompose === undefined) {
Object.defineProperty(this._storage.control, 'enviromentHasDockerCompose', {
get() {
if (this._storage.control._enviromentHasDockerCompose === undefined) {
const { exitCode } = execaCommandSync('docker compose version', { reject: false, stdio: 'pipe' });
this._storage.control._enviromentHasDockerCompose = exitCode === 0;
}
return this._storage.control._enviromentHasDockerCompose;
},
});
}

customizeRemoveFiles = this._storage.control.customizeRemoveFiles;
}

Expand Down
1 change: 1 addition & 0 deletions generators/base/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type Control = BaseApplicationControlProperties & {
*/
blueprintConfigured?: boolean;
reproducibleLiquibaseTimestamp?: Date;
enviromentHasDockerCompose?: boolean;
filterEntitiesForClient?: (entity: Entity[]) => Entity[];
filterEntitiesAndPropertiesForClient?: (entity: Entity[]) => Entity[];
filterEntityPropertiesForClient?: (entity: Entity) => Entity;
Expand Down
5 changes: 2 additions & 3 deletions generators/docker-compose/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,10 @@ export default class DockerComposeGenerator extends BaseWorkspacesGenerator {
this.log.log(chalk.white(`Files will be generated in folder: ${chalk.yellow(this.destinationRoot())}`));
},
checkDocker,
async checkDockerCompose() {
async checkDockerCompose({ control }) {
if (this.skipChecks) return;

const { exitCode } = await this.spawnCommand('docker compose version', { reject: false, stdio: 'pipe' });
if (exitCode !== 0) {
if (!control.enviromentHasDockerCompose) {
throw new Error(`Docker Compose V2 is not installed on your computer.
Read https://docs.docker.com/compose/install/
`);
Expand Down
26 changes: 26 additions & 0 deletions generators/docker/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright 2013-2024 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see https://www.jhipster.tech/
* for more information.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { JHipsterCommandDefinition } from '../../lib/command/index.js';

const command = {
configs: {},
import: [],
} as const satisfies JHipsterCommandDefinition;

export default command;
71 changes: 42 additions & 29 deletions generators/docker/generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
/**
* Copyright 2013-2024 the original author or authors from the JHipster project.
*
Expand Down Expand Up @@ -57,45 +56,46 @@ export default class DockerGenerator extends BaseApplicationGenerator {
get preparing() {
return this.asPreparingTaskGroup({
dockerServices({ application }) {
const dockerServices = application.dockerServices!;
if (application.backendTypeSpringBoot) {
application.dockerServices.push('app');
dockerServices.push('app');
}
if (application.authenticationTypeOauth2) {
application.dockerServices.push('keycloak');
dockerServices.push('keycloak');
}
if (application.searchEngineElasticsearch) {
application.dockerServices.push('elasticsearch');
dockerServices.push('elasticsearch');
}
if (application.messageBrokerKafka || application.messageBrokerPulsar) {
application.dockerServices.push(application.messageBroker);
dockerServices.push(application.messageBroker!);
}
if (application.serviceDiscoveryConsul || application.serviceDiscoveryEureka) {
application.dockerServices.push(application.serviceDiscoveryType);
dockerServices.push(application.serviceDiscoveryType!);
}
if (application.serviceDiscoveryAny || application.applicationTypeGateway || application.applicationTypeMicroservice) {
application.dockerServices.push('zipkin');
dockerServices.push('zipkin');
}
if (application.enableSwaggerCodegen) {
application.dockerServices.push('swagger-editor');
dockerServices.push('swagger-editor');
}
if (application.cacheProviderMemcached || application.cacheProviderRedis || application.cacheProviderHazelcast) {
application.dockerServices.push(application.cacheProvider);
dockerServices.push(application.cacheProvider!);
}
if (
application.databaseTypeCassandra ||
application.databaseTypeCouchbase ||
application.databaseTypeMongodb ||
application.databaseTypeNeo4j
) {
application.dockerServices.push(application.databaseType);
dockerServices.push(application.databaseType!);
}
if (
application.prodDatabaseTypePostgresql ||
application.prodDatabaseTypeMariadb ||
application.prodDatabaseTypeMysql ||
application.prodDatabaseTypeMssql
) {
application.dockerServices.push(application.prodDatabaseType);
dockerServices.push(application.prodDatabaseType);
}
},
addAppServices({ application, source }) {
Expand Down Expand Up @@ -172,13 +172,13 @@ export default class DockerGenerator extends BaseApplicationGenerator {
get postWriting() {
return this.asPostWritingTaskGroup({
async dockerServices({ application, source }) {
if (application.dockerServices.includes('cassandra')) {
const serviceName = application.databaseType;
source.addDockerExtendedServiceToApplicationAndServices(
if (application.dockerServices!.includes('cassandra')) {
const serviceName = application.databaseType!;
source.addDockerExtendedServiceToApplicationAndServices!(
{ serviceName },
{ serviceFile: './cassandra.yml', serviceName: 'cassandra-migration' },
);
source.addDockerDependencyToApplication(
source.addDockerDependencyToApplication!(
{ serviceName, condition: SERVICE_HEALTHY },
{ serviceName: 'cassandra-migration', condition: SERVICE_COMPLETED_SUCCESSFULLY },
);
Expand All @@ -187,48 +187,48 @@ export default class DockerGenerator extends BaseApplicationGenerator {
for (const serviceName of intersection(['postgresql', 'mysql', 'mariadb', 'mssql'], application.dockerServices)) {
// Blank profile services starts if no profile is passed.
const profiles = application.prodDatabaseType === application.devDatabaseType ? undefined : ['', 'prod'];
source.addDockerExtendedServiceToApplication({ serviceName });
source.addDockerExtendedServiceToServices({ serviceName, additionalConfig: { profiles } });
source.addDockerDependencyToApplication({ serviceName, condition: SERVICE_HEALTHY });
source.addDockerExtendedServiceToApplication!({ serviceName });
source.addDockerExtendedServiceToServices!({ serviceName, additionalConfig: { profiles } });
source.addDockerDependencyToApplication!({ serviceName, condition: SERVICE_HEALTHY });
}

for (const serviceName of intersection(
['couchbase', 'mongodb', 'neo4j', 'elasticsearch', 'keycloak'],
application.dockerServices,
)) {
source.addDockerExtendedServiceToApplicationAndServices({ serviceName });
source.addDockerDependencyToApplication({ serviceName, condition: SERVICE_HEALTHY });
source.addDockerExtendedServiceToApplicationAndServices!({ serviceName });
source.addDockerDependencyToApplication!({ serviceName, condition: SERVICE_HEALTHY });
}

for (const serviceName of application.dockerServices.filter(service => ['redis', 'memcached', 'pulsar'].includes(service))) {
source.addDockerExtendedServiceToApplicationAndServices({ serviceName });
for (const serviceName of application.dockerServices!.filter(service => ['redis', 'memcached', 'pulsar'].includes(service))) {
source.addDockerExtendedServiceToApplicationAndServices!({ serviceName });
}

if (application.dockerServices.includes('eureka')) {
if (application.dockerServices!.includes('eureka')) {
const depends_on = application.authenticationTypeOauth2
? {
keycloak: {
condition: SERVICE_HEALTHY,
},
}
: undefined;
source.addDockerExtendedServiceToApplicationAndServices({
source.addDockerExtendedServiceToApplicationAndServices!({
serviceName: 'jhipster-registry',
additionalConfig: {
depends_on,
},
});

source.addDockerDependencyToApplication({ serviceName: 'jhipster-registry', condition: SERVICE_HEALTHY });
source.addDockerDependencyToApplication!({ serviceName: 'jhipster-registry', condition: SERVICE_HEALTHY });
}
if (application.dockerServices.includes('consul')) {
source.addDockerExtendedServiceToApplicationAndServices(
if (application.dockerServices!.includes('consul')) {
source.addDockerExtendedServiceToApplicationAndServices!(
{ serviceName: 'consul' },
{ serviceFile: './consul.yml', serviceName: 'consul-config-loader' },
);
}
if (application.dockerServices.includes('kafka')) {
source.addDockerExtendedServiceToApplicationAndServices({ serviceName: 'kafka' });
if (application.dockerServices!.includes('kafka')) {
source.addDockerExtendedServiceToApplicationAndServices!({ serviceName: 'kafka' });
}
},

Expand Down Expand Up @@ -302,6 +302,19 @@ export default class DockerGenerator extends BaseApplicationGenerator {
return this.asPostWritingTaskGroup(this.delegateToBlueprint ? {} : this.postWriting);
}

get end() {
return this.asEndTaskGroup({
async dockerComposeUp({ control }) {
if (control.enviromentHasDockerCompose) {
this.log('');
this.log.warn(
'Docker Compose V2 is not installed on your computer. Some features may not work as expected. Read https://docs.docker.com/compose/install/',
);
}
},
});
}

/**
* @private
* Returns the JDBC URL for a databaseType
Expand Down
1 change: 1 addition & 0 deletions generators/docker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { default as command } from './command.js';
export { default } from './generator.js';
4 changes: 3 additions & 1 deletion generators/docker/support/docker-compose-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DockerComposeService } from '../types.js';

/**
* Creates EditFileCallback that creates a base docker compose yml file if empty.
*
Expand All @@ -28,7 +30,7 @@ export const createDockerComposeFile = (
name: ${name}
`;

export const createDockerExtendedServices = (...services) => ({
export const createDockerExtendedServices = (...services: DockerComposeService[]) => ({
services: Object.fromEntries(
services.map(({ serviceName, serviceFile = `./${serviceName}.yml`, extendedServiceName = serviceName, additionalConfig = {} }) => [
serviceName,
Expand Down
18 changes: 18 additions & 0 deletions generators/docker/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type DockerComposeService = {
serviceName: string;
serviceFile?: string;
condition?: string;
additionalConfig?: any;
extendedServiceName?: string;
};

export type DockerApplicationType = {
keycloakSecrets?: string[];
};

export type DockerSourceType = {
addDockerExtendedServiceToApplicationAndServices?(...services: DockerComposeService[]): void;
addDockerExtendedServiceToServices?(...services: DockerComposeService[]): void;
addDockerExtendedServiceToApplication?(...services: DockerComposeService[]): void;
addDockerDependencyToApplication?(...services: DockerComposeService[]): void;
};
3 changes: 3 additions & 0 deletions generators/java/generators/build-tool/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ export default class BuildToolGenerator extends BaseApplicationGenerator {
}
};
},
executable({ application }) {
application.buildToolExecutable = application.buildToolGradle ? 'gradlew' : 'mvnw';
},
});
}

Expand Down
1 change: 1 addition & 0 deletions generators/java/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type JavaNeedleOptions = GradleNeedleOptions;

export type JavaApplication = JavaBootstrapStorageProperties &
GradleApplication & {
buildToolExecutable: string;
javaVersion: string;

packageFolder: string;
Expand Down
2 changes: 1 addition & 1 deletion generators/server/support/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const SQL_DB_OPTIONS = [
value: databaseTypes.MSSQL,
name: 'Microsoft SQL Server',
},
];
] as { value: string; name: string }[];

/**
* Get DB type from DB value
Expand Down
26 changes: 20 additions & 6 deletions generators/spring-boot/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,18 +621,32 @@ public void set${javaBeanCase(propertyName)}(${propertyType} ${propertyName}) {

get end() {
return this.asEndTaskGroup({
end({ application }) {
end({ application, control }) {
const { buildToolExecutable } = application;
this.log.ok('Spring Boot application generated successfully.');

let executable = 'mvnw';
if (application.buildToolGradle) {
executable = 'gradlew';
if (application.dockerServices?.length && !control.enviromentHasDockerCompose) {
const dockerComposeCommand = chalk.yellow.bold('docker compose');
this.log('');
this.log
.warn(`${dockerComposeCommand} command was not found in your environment. The generated Spring Boot application uses ${dockerComposeCommand} integration by default. You can disable it by setting
${chalk.yellow.bold(`
spring:
docker:
compose:
enabled: false
`)}
in your ${chalk.yellow.bold(`${application.srcMainResources}config/application.yml`)} file or removing 'spring-boot-docker-compose' dependency.
`);
}

let logMsgComment = '';
if (os.platform() === 'win32') {
logMsgComment = ` (${chalk.yellow.bold(executable)} if using Windows Command Prompt)`;
logMsgComment = ` (${chalk.yellow.bold(buildToolExecutable)} if using Windows Command Prompt)`;
}
this.log.log(chalk.green(` Run your Spring Boot application:\n ${chalk.yellow.bold(`./${executable}`)}${logMsgComment}`));
this.log.log(
chalk.green(` Run your Spring Boot application:\n ${chalk.yellow.bold(`./${buildToolExecutable}`)}${logMsgComment}`),
);
},
});
}
Expand Down
Loading

0 comments on commit 2b84fed

Please sign in to comment.