Skip to content

Commit

Permalink
Merge pull request #25817 from mshima/gateway-imperative
Browse files Browse the repository at this point in the history
Add manual routes support to imperative gateway
  • Loading branch information
DanielFran committed May 6, 2024
2 parents 49658c0 + cf9d21b commit cc67bdc
Show file tree
Hide file tree
Showing 24 changed files with 232 additions and 25 deletions.
4 changes: 4 additions & 0 deletions generators/app/__snapshots__/generator.spec.ts.snap
Expand Up @@ -450,6 +450,7 @@ exports[`generator - app with default config should match snapshot 1`] = `
"fakerSeed": undefined,
"feignClient": undefined,
"frontendAppName": "jhipsterApp",
"gatewayRoutes": undefined,
"gatewayServerPort": undefined,
"gatlingTests": false,
"generateAuthenticationApi": true,
Expand Down Expand Up @@ -1044,6 +1045,7 @@ exports[`generator - app with gateway should match snapshot 1`] = `
"fakerSeed": undefined,
"feignClient": undefined,
"frontendAppName": "jhipsterApp",
"gatewayRoutes": [],
"gatewayServerPort": undefined,
"gatlingTests": false,
"generateAuthenticationApi": true,
Expand Down Expand Up @@ -1327,6 +1329,7 @@ exports[`generator - app with gateway should match snapshot 1`] = `
"projectVersion": "0.0.1-SNAPSHOT",
"reactive": true,
"rememberMeKey": undefined,
"routes": undefined,
"searchEngine": "no",
"searchEngineAny": false,
"searchEngineCouchbase": false,
Expand Down Expand Up @@ -1632,6 +1635,7 @@ exports[`generator - app with microservice should match snapshot 1`] = `
"fakerSeed": undefined,
"feignClient": undefined,
"frontendAppName": "jhipsterApp",
"gatewayRoutes": undefined,
"gatewayServerPort": undefined,
"gatlingTests": false,
"generateAuthenticationApi": false,
Expand Down
1 change: 1 addition & 0 deletions generators/bootstrap-application-server/generator.ts
Expand Up @@ -105,6 +105,7 @@ export default class BoostrapApplicationServer extends BaseApplicationGenerator
...applicationDockerContainers,
...currentDockerContainers,
}),
gatewayRoutes: undefined,
});
},
});
Expand Down
2 changes: 2 additions & 0 deletions generators/server/jdl/application-definition.ts
Expand Up @@ -21,12 +21,14 @@ import { JDLApplicationConfig, JHipsterOptionDefinition } from '../../../jdl/typ
import databaseMigrationOption from '../options/database-migration.js';
import messageBrokerOption from '../options/message-broker.js';
import { feignClientDefinition, syncUserWithIdpDefinition } from '../options/index.js';
import { jdlRoutesOptions } from '../../spring-cloud/generators/gateway/jdl/jdl-routes-option.js';

const jdlOptions: JHipsterOptionDefinition[] = [
databaseMigrationOption,
messageBrokerOption,
feignClientDefinition,
syncUserWithIdpDefinition,
jdlRoutesOptions,
];

const applicationConfig: JDLApplicationConfig = {
Expand Down
3 changes: 3 additions & 0 deletions generators/server/templates/build.gradle.ejs
Expand Up @@ -237,6 +237,9 @@ if (addSpringMilestoneRepository) { _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryEureka) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-config"
<%_ } _%>
<%_ if (applicationTypeGateway && !reactive) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-loadbalancer"
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryConsul) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-consul-config"
<%_ } _%>
Expand Down
6 changes: 6 additions & 0 deletions generators/server/templates/pom.xml.ejs
Expand Up @@ -282,6 +282,12 @@
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<%_ } _%>
<%_ if (applicationTypeGateway && !reactive) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryConsul) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
Expand Down
Expand Up @@ -188,6 +188,22 @@ spring:
enabled: false
<%_ } _%>
<%_ if (applicationTypeGateway) { _%>
<%_ if (!serviceDiscoveryAny) { _%>
discovery:
client:
simple:
instances:
<%= lowercaseBaseName %>:
- instanceId: <%= lowercaseBaseName %>1
host: localhost
port: <%= serverPort %>
<%_ for (const ms of gatewayRoutes ?? []) { _%>
<%= ms.route %>:
- instanceId: <%= ms.route %>1
host: <%= ms.host %>
port: <%= ms.serverPort %>
<%_ } _%>
<%_ } _%>
gateway:
<%_ if (reactive) { _%>
default-filters:
Expand All @@ -212,11 +228,22 @@ spring:
mvc:
routes:
- id: <%= lowercaseBaseName %>_route
uri: lb://<%= lowercaseBaseName %>:<%= serverPort %>
uri: lb://<%= lowercaseBaseName %>
predicates:
- Path=/services/<%= lowercaseBaseName %>/**
filters:
- RewritePath=/services/<%= lowercaseBaseName %>/?(?<segment>.*), /$\{segment}
- StripPrefix=2
<%_ for (const ms of gatewayRoutes ?? []) { _%>
- id: <%= ms.route %>_route
uri: lb://<%= ms.route %>
predicates:
- Path=/services/<%= ms.route %>/**
filters:
- StripPrefix=2
<%_ if (authenticationTypeOauth2) { _%>
- TokenRelay
<%_ } _%>
<%_ } _%>
<%_ } _%>
<%_ } _%>
<%_ if (messageBrokerKafka) { _%>
Expand Down
2 changes: 2 additions & 0 deletions generators/server/types.d.ts
Expand Up @@ -6,6 +6,7 @@ import { SpringCacheSourceType } from '../spring-cache/types.js';
import { MessageBrokerApplicationType } from './options/message-broker.js';
import type { DeterministicOptionWithDerivedProperties, OptionWithDerivedProperties } from '../base-application/application-options.js';
import { ApplicationPropertiesNeedles } from './support/needles.ts';
import { GatewayApplication } from '../spring-cloud/generators/gateway/types.ts';

export type SpringEntity = {
/* Generate entity's Entity */
Expand Down Expand Up @@ -91,6 +92,7 @@ export type SpringBootApplication = JavaApplication &
BuildToolApplication &
SearchEngine &
DatabaseTypeApplication &
GatewayApplication &
MessageBrokerApplicationType & {
jhipsterDependenciesVersion: string;
springBootDependencies: Record<string, string>;
Expand Down
6 changes: 5 additions & 1 deletion generators/spring-cloud/generators/gateway/command.ts
Expand Up @@ -19,7 +19,11 @@
import type { JHipsterCommandDefinition } from '../../../base/api.js';

const command: JHipsterCommandDefinition = {
configs: {},
configs: {
routes: {
scope: 'storage',
},
},
import: [],
};

Expand Down
15 changes: 15 additions & 0 deletions generators/spring-cloud/generators/gateway/generator.ts
Expand Up @@ -89,6 +89,21 @@ export default class GatewayGenerator extends BaseApplicationGenerator {
return this.delegateTasksToBlueprint(() => this.loading);
}

get preparing() {
return this.asPreparingTaskGroup({
prepareGateway({ application }) {
application.gatewayRoutes = (application.routes ?? []).map(routeDef => {
const [route, host = route, serverPort = '8080'] = routeDef.split(':');
return { route, serverPort, host };
});
},
});
}

get [BaseApplicationGenerator.PREPARING]() {
return this.delegateTasksToBlueprint(() => this.preparing);
}

get writing() {
return this.asWritingTaskGroup({
async writing({ application }) {
Expand Down
@@ -0,0 +1,75 @@
import { before, it, describe, expect } from 'esmocha';
import { createImporterFromContent, ImportState } from '../../../../../jdl/jdl-importer.js';
import { convertSingleContentToJDL } from '../../../../../jdl/converters/json-to-jdl-converter.js';

const optionName = 'routes';

describe('generators - spring-cloud:gateway - jdl', () => {
it('should not accept route and port', () => {
expect(() => createImporterFromContent(`application { config { ${optionName} ["blog:123"] } }`)).toThrow(
/The routes property name must match:/,
);
});
it('should not accept values starting with numbers', () => {
expect(() => createImporterFromContent(`application { config { ${optionName} ["1foo"] } }`)).toThrow(
/The routes property name must match:/,
);
});
it('should not accept empty value', () => {
expect(() => createImporterFromContent(`application { config { ${optionName} [""] } }`)).toThrow(
/The routes property name must match:/,
);
});
it('should not accept empty host', () => {
expect(() => createImporterFromContent(`application { config { ${optionName} ["foo:"] } }`)).toThrow(
/The routes property name must match:/,
);
});
it('should not accept empty port', () => {
expect(() => createImporterFromContent(`application { config { ${optionName} ["foo:foo_host:"] } }`)).toThrow(
/The routes property name must match:/,
);
});
it('should not accept non numeric port', () => {
expect(() => createImporterFromContent(`application { config { ${optionName} ["foo:foo_host:1a"] } }`)).toThrow(
/The routes property name must match:/,
);
});
describe(`parsing ${optionName}`, () => {
let state: ImportState;

before(() => {
const importer = createImporterFromContent(
`application { config { ${optionName} ["blog:blog_host:123", "store:store_host", "notification"] } }`,
);
state = importer.import();
});

it('should set expected value', () => {
expect(state.exportedApplicationsWithEntities.jhipster.config[optionName]).toEqual([
'blog:blog_host:123',
'store:store_host',
'notification',
]);
});
});
describe(`export ${optionName}`, () => {
let jdl: string;

before(() => {
jdl = convertSingleContentToJDL({
'generator-jhipster': { baseName: 'bar', [optionName]: ['blog:blog_host:123', 'store:store_host', 'notification'] },
});
});

it('should set expected value', () => {
expect(jdl).toEqual(`application {
config {
baseName bar
routes ["blog:blog_host:123", "store:store_host", "notification"]
}
}
`);
});
});
});
@@ -0,0 +1,8 @@
import { JHipsterOptionDefinition } from '../../../../../jdl/types/types.js';

export const jdlRoutesOptions: JHipsterOptionDefinition = {
name: 'routes',
tokenType: 'quotedList',
type: 'quotedList',
tokenValuePattern: /^"[A-Za-z][A-Za-z0-9_]*(?::[A-Za-z][A-Za-z0-9_]+(?::[0-9]+)?)?"$/,
};
4 changes: 4 additions & 0 deletions generators/spring-cloud/generators/gateway/types.d.ts
@@ -0,0 +1,4 @@
export type GatewayApplication = {
gatewayRoutes?: Array<{ route: string; host: string; serverPort: string }>;
routes?: string[];
};
Expand Up @@ -531,6 +531,7 @@ JDLApplication {
},
"languages": ListJDLApplicationConfigurationOption {
"name": "languages",
"quoted": false,
"value": Set {},
},
"packageFolder": StringJDLApplicationConfigurationOption {
Expand Down
1 change: 1 addition & 0 deletions jdl/models/jdl-application-configuration-factory.spec.ts
Expand Up @@ -128,6 +128,7 @@ JDLApplicationConfiguration {
"options": {
"testFrameworks": ListJDLApplicationConfigurationOption {
"name": "testFrameworks",
"quoted": false,
"value": Set {
"gatling",
},
Expand Down
2 changes: 2 additions & 0 deletions jdl/models/jdl-application-configuration-factory.ts
Expand Up @@ -84,6 +84,8 @@ function createJDLConfigurationOption(type: string, name: string, value: any) {
return new BooleanJDLApplicationConfigurationOption(name, value);
case 'list':
return new ListJDLApplicationConfigurationOption(name, value);
case 'quotedList':
return new ListJDLApplicationConfigurationOption(name, value, true);
/* istanbul ignore next */
default:
// It should not happen! This is a developer error.
Expand Down
2 changes: 1 addition & 1 deletion jdl/models/jdl-application-definition.ts
@@ -1,7 +1,7 @@
import { jhipsterOptionTypes, jhipsterOptionValues, jhipsterQuotedOptionNames } from '../jhipster/application-options.js';

export type JDLApplicationOptionValue = string | number | boolean | undefined | never[] | Record<string, string>;
export type JDLApplicationOptionTypeValue = 'string' | 'integer' | 'boolean' | 'list';
export type JDLApplicationOptionTypeValue = 'string' | 'integer' | 'boolean' | 'list' | 'quotedList';
export type JDLApplicationOptionType = { type: JDLApplicationOptionTypeValue };

export default class JDLApplicationDefinition {
Expand Down
7 changes: 5 additions & 2 deletions jdl/models/list-jdl-application-configuration-option.ts
Expand Up @@ -21,15 +21,18 @@ import JDLApplicationConfigurationOption from './jdl-application-configuration-o
import { join } from '../utils/set-utils.js';

export default class ListJDLApplicationConfigurationOption extends JDLApplicationConfigurationOption {
constructor(name, value) {
quoted: boolean;

constructor(name: string, value: string[], quoted: boolean = false) {
super(name, new Set(value));
this.quoted = quoted;
}

getValue(): any[] {
return Array.from(this.value);
}

toString() {
return `${this.name} [${join(this.value, ', ')}]`;
return `${this.name} [${join(this.value, ', ', this.quoted)}]`;
}
}
13 changes: 13 additions & 0 deletions jdl/parsing/jdl-ast-builder-visitor.ts
Expand Up @@ -581,6 +581,9 @@ export default class JDLAstBuilderVisitor extends BaseJDLCSTVisitor {
if (context.list) {
return this.visit(context.list);
}
if (context.quotedList) {
return this.visit(context.quotedList);
}
if (context.INTEGER) {
return context.INTEGER[0].image;
}
Expand Down Expand Up @@ -631,6 +634,9 @@ export default class JDLAstBuilderVisitor extends BaseJDLCSTVisitor {
if (context.list) {
return this.visit(context.list);
}
if (context.quotedList) {
return this.visit(context.quotedList);
}
if (context.INTEGER) {
return context.INTEGER[0].image;
}
Expand All @@ -656,6 +662,13 @@ export default class JDLAstBuilderVisitor extends BaseJDLCSTVisitor {
}
return context.NAME.map(namePart => namePart.image, this);
}

quotedList(context) {
if (!context.STRING) {
return [];
}
return context.STRING.map(namePart => namePart.image.slice(1, -1), this);
}
}

function getOptionEntityAndExcludedEntityLists(astResult, option) {
Expand Down
15 changes: 15 additions & 0 deletions jdl/parsing/jdl-parser.ts
Expand Up @@ -76,6 +76,7 @@ export default class JDLParser extends CstParser {
this.applicationNamespaceConfigDeclaration();
this.namespaceConfigValue();
this.qualifiedName();
this.quotedList();
this.list();

// very important to call this after all the rules have been defined.
Expand Down Expand Up @@ -587,6 +588,7 @@ export default class JDLParser extends CstParser {
this.OR([
{ ALT: () => this.CONSUME(LexerTokens.BOOLEAN) },
{ ALT: () => this.SUBRULE(this.qualifiedName) },
{ ALT: () => this.SUBRULE(this.quotedList) },
{ ALT: () => this.SUBRULE(this.list) },
{ ALT: () => this.CONSUME(LexerTokens.INTEGER) },
{ ALT: () => this.CONSUME(LexerTokens.STRING) },
Expand Down Expand Up @@ -618,6 +620,19 @@ export default class JDLParser extends CstParser {
});
}

quotedList(): any {
this.RULE('quotedList', () => {
this.CONSUME(LexerTokens.LSQUARE);
this.AT_LEAST_ONE_SEP({
SEP: LexerTokens.COMMA,
DEF: () => {
this.CONSUME(LexerTokens.STRING);
},
});
this.CONSUME(LexerTokens.RSQUARE);
});
}

applicationSubEntities(): any {
this.RULE('applicationSubEntities', () => {
this.CONSUME(LexerTokens.ENTITIES);
Expand Down

0 comments on commit cc67bdc

Please sign in to comment.