Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spring-cloud:gateway generator and initial imperative gateway support. #25768

Merged
merged 3 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions generators/server/__test-support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const mockedGenerators = [
`jhipster:${GENERATOR_MAVEN}:code-quality`,
`jhipster:${GENERATOR_MAVEN}:jib`,
`jhipster:${GENERATOR_MAVEN}:node`,
'jhipster:spring-cloud:gateway',
`jhipster:${GENERATOR_SPRING_DATA_CASSANDRA}`,
`jhipster:${GENERATOR_SPRING_DATA_MONGODB}`,
`jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`,
Expand Down
33 changes: 1 addition & 32 deletions generators/server/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ export default class JHipsterServerGenerator extends BaseApplicationGenerator {
const excludeWebapp = application.skipClient ? '' : ' -Dskip.installnodenpm -Dskip.npm';
scriptsStorage.set({
'app:start': './mvnw',
'backend:info': './mvnw -ntp enforcer:display-info --batch-mode',
'backend:info': './mvnw --version',
'backend:doc:test': './mvnw -ntp javadoc:javadoc --batch-mode',
'backend:nohttp:test': './mvnw -ntp checkstyle:check --batch-mode',
'backend:start': `./mvnw${excludeWebapp}`,
Expand Down Expand Up @@ -637,37 +637,6 @@ export default class JHipsterServerGenerator extends BaseApplicationGenerator {
return this.delegateTasksToBlueprint(() => this.postWriting);
}

get postWritingEntities() {
return this.asPostWritingEntitiesTaskGroup({
packageJsonE2eScripts({ application, entities }) {
if (application.applicationTypeGateway) {
const { serverPort, lowercaseBaseName } = application;
const microservices = [...new Set(entities.map(entity => entity.microserviceName))].filter(Boolean).map(ms => ms.toLowerCase());
const scriptsStorage = this.packageJson.createStorage('scripts');
const waitServices = microservices
.concat(lowercaseBaseName)
.map(ms => `npm run ci:server:await:${ms}`)
.join(' && ');

scriptsStorage.set({
[`ci:server:await:${lowercaseBaseName}`]: `wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:$npm_package_config_backend_port/management/health`,
...Object.fromEntries(
microservices.map(ms => [
`ci:server:await:${ms}`,
`wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:${serverPort}/services/${ms}/management/health/readiness`,
]),
),
'ci:server:await': `echo "Waiting for services to start" && ${waitServices} && echo "Services started"`,
});
}
},
});
}

get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() {
return this.delegateTasksToBlueprint(() => this.postWritingEntities);
}

_configureServer(config = this.jhipsterConfigWithDefaults, dest = this.jhipsterConfig) {
// Generate JWT secret key if key does not already exist in config
if (
Expand Down
3 changes: 0 additions & 3 deletions generators/server/templates/build.gradle.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,6 @@ if (addSpringMilestoneRepository) { _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryConsul) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-consul-discovery"
<%_ } _%>
<%_ if (applicationTypeGateway) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-gateway"
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryEureka) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-netflix-eureka-client"
<%_ } _%>
Expand Down
13 changes: 4 additions & 9 deletions generators/server/templates/pom.xml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -263,17 +263,18 @@
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<%_ } _%>
<%_ if ((applicationTypeMicroservice || applicationTypeGateway) && reactive) { _%>
<%_ if (applicationTypeMicroservice || applicationTypeGateway) { _%>
<%_ if (reactive) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<%_ } _%>
<%_ if ((applicationTypeMicroservice || applicationTypeGateway) && !reactive) { _%>
<%_ } else { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<%_ } _%>
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryEureka) { _%>
<dependency>
Expand All @@ -291,12 +292,6 @@
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<%_ } _%>
<%_ if (applicationTypeGateway) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryEureka) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ import <%= packageName %>.security.oauth2.JwtGrantedAuthorityConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
<%_ } _%>
<%_ if (authenticationTypeOauth2 && applicationTypeMonolith) { _%>
<%_ if (authenticationTypeOauth2 && !applicationTypeMicroservice) { _%>
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.boot.web.client.RestTemplateBuilder;
import <%= packageName %>.security.oauth2.CustomClaimConverter;
Expand Down Expand Up @@ -221,6 +221,18 @@ public class SecurityConfiguration {
<%_ } _%>
.requestMatchers(mvc.pattern("/api/admin/**")).hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers(mvc.pattern("/api/**")).authenticated()
<%_ if (applicationTypeGateway) { _%>
<%_ if (microfrontend) { _%>
// microfrontend resources are loaded by webpack without authentication, they need to be public
.requestMatchers(mvc.pattern("/services/*/*.js")).permitAll()
.requestMatchers(mvc.pattern("/services/*/*.txt")).permitAll()
.requestMatchers(mvc.pattern("/services/*/*.json")).permitAll()
.requestMatchers(mvc.pattern("/services/*/*.js.map")).permitAll()
<%_ } _%>
.requestMatchers(mvc.pattern("/services/*/management/health/readiness")).permitAll()
.requestMatchers(mvc.pattern("/services/*/v3/api-docs")).hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers(mvc.pattern("/services/**")).authenticated()
<%_ } _%>
<%_ if (communicationSpringWebsocket) { _%>
.requestMatchers(mvc.pattern("/websocket/**")).authenticated()
<%_ } _%>
Expand Down Expand Up @@ -267,9 +279,9 @@ public class SecurityConfiguration {
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
<%_ } else if (authenticationTypeOauth2) { _%>
<%_ if (applicationTypeMonolith) { _%>
<%_ if (!applicationTypeMicroservice) { _%>
.oauth2Login(oauth2 -> oauth2.loginPage("/").userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService())))
<%_ } else if (applicationTypeMicroservice) { _%>
<%_ } else { _%>
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
<%_ } _%>
.oauth2ResourceServer(oauth2 -> oauth2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ public class SpaWebFilter extends OncePerRequestFilter {
<%_ if (devDatabaseTypeH2Any) { _%>
!path.startsWith("/h2-console") &&
<%_ } _%>
<%_ if (authenticationTypeOauth2 && applicationTypeMonolith) { _%>
<%_ if (authenticationTypeOauth2 && (applicationTypeGateway || applicationTypeMonolith)) { _%>
!path.startsWith("/login") &&
!path.startsWith("/oauth2") &&
<%_ } _%>
<%_ if (applicationTypeGateway) { _%>
!path.startsWith("/services") &&
<%_ } _%>
<%_ if (communicationSpringWebsocket) { _%>
!path.startsWith("/websocket") &&
<%_ } _%>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ eureka:
git-branch: ${git.branch:}
context-path: ${server.servlet.context-path:}
<%_ } _%>
<%_ if (applicationTypeGateway || (applicationTypeMicroservice && reactive)) { _%>
<%_ if ((applicationTypeGateway || applicationTypeMicroservice) && reactive) { _%>
reactive:
feign:
circuit:
Expand Down Expand Up @@ -99,7 +99,7 @@ management:
include:
- configprops
- env
<% if (applicationTypeGateway && reactive) { -%>
<% if (applicationTypeGateway) { -%>
- gateway
<% } -%>
- health
Expand Down Expand Up @@ -175,7 +175,7 @@ spring:
<%_ } _%>
application:
name: <%= baseName %>
<%_ if (serviceDiscoveryConsul || (applicationTypeGateway && reactive) || messageBrokerKafka) { _%>
<%_ if (serviceDiscoveryConsul || applicationTypeGateway || messageBrokerKafka) { _%>
cloud:
<%_ if (serviceDiscoveryConsul) { _%>
consul:
Expand All @@ -187,8 +187,9 @@ spring:
watch:
enabled: false
<%_ } _%>
<%_ if (applicationTypeGateway && reactive) { _%>
<%_ if (applicationTypeGateway) { _%>
gateway:
<%_ if (reactive) { _%>
default-filters:
- <% if (authenticationTypeJwt) { %>JWTRelay<% } else { %>TokenRelay<% } %>
discovery:
Expand All @@ -207,6 +208,16 @@ spring:
httpclient:
pool:
max-connections: 1000
<%_ } else { _%>
mvc:
routes:
- id: <%= lowercaseBaseName %>_route
uri: lb://<%= lowercaseBaseName %>:<%= serverPort %>
predicates:
- Path=/services/<%= lowercaseBaseName %>/**
filters:
- RewritePath=/services/<%= lowercaseBaseName %>/?(?<segment>.*), /$\{segment}
<%_ } _%>
<%_ } _%>
<%_ if (messageBrokerKafka) { _%>
function:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ mongock:
spring:
application:
name: <%= baseName %>
<%_ if (applicationTypeGateway && reactive) { _%>
<%_ if (applicationTypeGateway) { _%>
autoconfigure:
exclude:
- org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration
Expand Down
24 changes: 0 additions & 24 deletions generators/spring-boot/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,29 +235,6 @@ const jwtFiles = {
],
};

const gatewayFiles = {
gatewayFiles: [
{
condition: generator => generator.authenticationTypeJwt,
path: `${SERVER_MAIN_SRC_DIR}_package_/`,
renameTo: moveToJavaPackageSrcDir,
templates: ['security/jwt/JWTRelayGatewayFilterFactory.java'],
},
{
condition: generator => generator.serviceDiscoveryAny,
path: `${SERVER_MAIN_SRC_DIR}_package_/`,
renameTo: moveToJavaPackageSrcDir,
templates: ['web/rest/vm/RouteVM.java', 'web/rest/GatewayResource.java', 'web/filter/ModifyServersOpenApiFilter.java'],
},
{
condition: generator => generator.serviceDiscoveryAny,
path: `${SERVER_TEST_SRC_DIR}_package_/`,
renameTo: moveToJavaPackageTestDir,
templates: ['web/filter/ModifyServersOpenApiFilterTest.java'],
},
],
};

const swaggerFiles = {
swagger: [
{
Expand Down Expand Up @@ -544,7 +521,6 @@ export const serverFiles = mergeSections(
baseServerFiles,
addSectionsCondition(jwtFiles, context => context.authenticationTypeJwt),
addSectionsCondition(oauth2Files, context => context.authenticationTypeOauth2),
addSectionsCondition(gatewayFiles, context => context.applicationTypeGateway),
addSectionsCondition(accountFiles, context => context.generateAuthenticationApi),
addSectionsCondition(userManagementFiles, context => context.generateUserManagement),
addSectionsCondition(imperativeConfigFiles, context => !context.reactive),
Expand Down
33 changes: 16 additions & 17 deletions generators/spring-boot/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,29 +115,12 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {

get configuring() {
return this.asConfiguringTaskGroup({
forceReactiveGateway() {
if (this.jhipsterConfig.applicationType === GATEWAY) {
if (this.jhipsterConfig.reactive !== undefined && !this.jhipsterConfig.reactive) {
this.log.warn('Non reactive gateway is not supported. Switching to reactive.');
}
this.jhipsterConfig.reactive = true;
}
},
checks() {
const config = this.jhipsterConfigWithDefaults;
if (config.enableHibernateCache && [NO_CACHE, MEMCACHED].includes(config.cacheProvider)) {
this.log.verboseInfo(`Disabling hibernate cache for cache provider ${config.cacheProvider}`);
this.jhipsterConfig.enableHibernateCache = false;
}

if (config.websocket && config.websocket !== NO_WEBSOCKET) {
if (config.reactive) {
throw new Error('Spring Websocket is not supported with reactive applications.');
}
if (config.applicationType === MICROSERVICE) {
throw new Error('Spring Websocket is not supported with microservice applications.');
}
}
},
feignMigration() {
const { reactive, applicationType, feignClient } = this.jhipsterConfigWithDefaults;
Expand Down Expand Up @@ -169,6 +152,7 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {
return this.asComposingTaskGroup({
async composing() {
const {
applicationType,
databaseType,
messageBroker,
searchEngine,
Expand All @@ -189,6 +173,10 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {
await this.composeWithJHipster('jhipster:java:node');
}

if (applicationType === GATEWAY) {
await this.composeWithJHipster('jhipster:spring-cloud:gateway');
}

if (enableTranslation) {
await this.composeWithJHipster(GENERATOR_LANGUAGES);
}
Expand Down Expand Up @@ -235,6 +223,17 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {

get preparing() {
return this.asPreparingTaskGroup({
checksWebsocket({ application }) {
const { websocket } = application as any;
if (websocket && websocket !== NO_WEBSOCKET) {
if (application.reactive) {
throw new Error('Spring Websocket is not supported with reactive applications.');
}
if (application.applicationType === MICROSERVICE) {
throw new Error('Spring Websocket is not supported with microservice applications.');
}
}
},
loadSpringBootBom({ application }) {
if (this.useVersionPlaceholders) {
application.springBootDependencies = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`generator - spring-cloud:gateway with defaults options should call source snapshot 1`] = `
{
"addJavaDependencies": [
[
{
"artifactId": "spring-cloud-starter-gateway",
"groupId": "org.springframework.cloud",
},
],
],
}
`;

exports[`generator - spring-cloud:gateway with defaults options should match files snapshot 1`] = `
{
".yo-rc.json": {
"stateCleared": "modified",
},
"src/main/java/com/mycompany/myapp/security/jwt/JWTRelayGatewayFilterFactory.java": {
"stateCleared": "modified",
},
}
`;

exports[`generator - spring-cloud:gateway with imperative option should call source snapshot 1`] = `
{
"addJavaDependencies": [
[
{
"artifactId": "spring-cloud-starter-gateway-mvc",
"groupId": "org.springframework.cloud",
},
],
],
}
`;

exports[`generator - spring-cloud:gateway with imperative option should match files snapshot 1`] = `
{
".yo-rc.json": {
"stateCleared": "modified",
},
}
`;

exports[`generator - spring-cloud:gateway with serviceDiscovery option should call source snapshot 1`] = `
{
"addJavaDependencies": [
[
{
"artifactId": "spring-cloud-starter-gateway",
"groupId": "org.springframework.cloud",
},
],
],
}
`;

exports[`generator - spring-cloud:gateway with serviceDiscovery option should match files snapshot 1`] = `
{
".yo-rc.json": {
"stateCleared": "modified",
},
"src/main/java/com/mycompany/myapp/security/jwt/JWTRelayGatewayFilterFactory.java": {
"stateCleared": "modified",
},
}
`;
Loading
Loading