Skip to content

Commit

Permalink
Add Support For Thin Jars in GAE (#10420)
Browse files Browse the repository at this point in the history
* add support for thin jar in gae

Only adds to maven build; work in progress

* correcting mistake introduced in a pervious pull request

A previous pull request incorrectly removes the com.google.cloud.tools when gaeCloudSQLInstanceNeeded is set to false

#10362

* add correct handlers for retrieving static files

* remove bootWar from gradle

The new GAE with Java 11 builds on JAR; this has been done on the maven side.

* correcting mistake introduced in a pervious pull request

A previous pull request incorrectly removes the com.google.cloud.tools when gaeCloudSQLInstanceNeeded is set to false

* replace deprecated value "project" by "projectId"

* testing with cloudsql; change string to single quotes

If this is not done the following error is created;

yntaxError: Multi-line double-quoted string needs to be sufficiently indented (16:14)
  14 |     datasource:
  15 |         type: com.zaxxer.hikari.HikariDataSource
> 16 |         url: "jdbc:mysql://google/mysql?cloudSqlInstance=abstract-block-253023:us-central1:testjhipster
     |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

* Add copyright information

* add mainclass name instead of basename

* add gradle support for unjaring the original jar

This adds Gradle side support for thin jars

* missing symbol from previous commit

* remove the original jar as per discussion

* minor bug fix per code review

* add max heap size to entrypoint field in app.yaml

* Revert Xmx change

* correction of the url strings

* remove whitespaces in project ID

* simplify the prod-gae profile definition

* thin jar generation support for maven

* thin jar generation support for gradle

* adjustments to the maven and gradle deployment commands

* add thinResolve step before staging

* prettier issue fix

* add error code checking for gcloud

- gcloud seems to print out info messages to the error stream as well. We need to check whether it's an actual error

* add trim to remove unnecessary newline and remove silent mode for prompt in order to trigger cloud sql api

* fix the trim line more elegantly

* add missing maven dependency

* refactor code to include application-prod-gae.yml file regardless of Cloud SQL

- this makes sure the correct port environment variable is passed whether the user chose to use cloud sql or not.

* fix dependency problem (similar to #9175) to get rid of the clean option

* add dependency before the check for cloud sql

* changed per code review
  • Loading branch information
SudharakaP authored and Matt Raible committed Dec 16, 2019
1 parent 08f36bb commit 7d21a02
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 45 deletions.
70 changes: 40 additions & 30 deletions generators/gae/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module.exports = class extends BaseGenerator {
const done = this.async();

shelljs.exec('gcloud version', { silent: true }, (code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.log.error(
"You don't have the Cloud SDK (gcloud) installed. \nDownload it from https://cloud.google.com/sdk/install"
);
Expand All @@ -66,7 +66,7 @@ module.exports = class extends BaseGenerator {
this.log(chalk.bold('\nInstalling App Engine Java SDK'));
this.log(`... Running: gcloud components install ${component} --quiet`);
shelljs.exec(`gcloud components install ${component} --quiet`, { silent: true }, (code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.log.error(err);
done(
`Installation failed. \nPlease try to install the app-engine-java component manually via; gcloud components install ${component}`
Expand All @@ -83,6 +83,7 @@ module.exports = class extends BaseGenerator {
const configuration = this.getAllJhipsterConfig(this, true);
this.env.options.appPath = configuration.get('appPath') || constants.CLIENT_MAIN_SRC_DIR;
this.baseName = configuration.get('baseName');
this.mainClass = this.getMainClassName();
this.packageName = configuration.get('packageName');
this.packageFolder = configuration.get('packageFolder');
this.cacheProvider = configuration.get('cacheProvider') || configuration.get('hibernateCache') || 'no';
Expand All @@ -107,6 +108,10 @@ module.exports = class extends BaseGenerator {
this.gaeMaxInstances = configuration.get('gaeMaxInstances');
this.gaeMinInstances = configuration.get('gaeMinInstances');
this.gaeCloudSQLInstanceNeeded = configuration.get('gaeCloudSQLInstanceNeeded');
this.CLIENT_DIST_DIR = this.getResourceBuildDirectoryForBuildTool(this.config.buildTool) + constants.CLIENT_DIST_DIR;
this.skipClient = this.config.get('skipClient');
this.clientPackageManager = this.config.get('clientPackageManager');
this.dasherizedBaseName = _.kebabCase(this.baseName);
}
};
}
Expand Down Expand Up @@ -148,23 +153,21 @@ module.exports = class extends BaseGenerator {
message: 'Google Cloud Project ID',
default: this.defaultProjectId(),
validate: input => {
if (input.length === 0) {
if (input.trim().length === 0) {
return 'Project ID cannot empty';
}
try {
shelljs.exec(`gcloud projects describe ${input}`, { silent: true });
this.gcpProjectIdExists = true;
} catch (ex) {
this.gcpProjectIdExists = false;
return `Project ID "${chalk.cyan(input)}" does not exist, please create one first!`;
return `Project ID "${chalk.cyan(input.trim())}" does not exist, please create one first!`;
}
return true;
}
}
];

this.prompt(prompts).then(props => {
this.gcpProjectId = props.gcpProjectId;
this.gcpProjectId = props.gcpProjectId.trim();
done();
});
},
Expand All @@ -177,7 +180,7 @@ module.exports = class extends BaseGenerator {
`gcloud app describe --format="value(locationId)" --project="${this.gcpProjectId}"`,
{ silent: true },
(code, stdout, err) => {
if (err) {
if (err && code !== 0) {
const prompts = [
{
type: 'list',
Expand Down Expand Up @@ -381,7 +384,7 @@ module.exports = class extends BaseGenerator {
prompts.push({
type: 'input',
name: 'gaeCloudSQLInstanceNeeded',
message: 'Initialize a new Cloud SQL instance (Y/N) ?',
message: 'Use a Cloud SQL instance (Y/N) ?',
default: this.gaeCloudSQLInstanceNeeded ? this.gaeCloudSQLInstanceNeeded : 'Y',
validate: input => {
if (input !== 'Y' && input !== 'N') {
Expand All @@ -408,9 +411,8 @@ module.exports = class extends BaseGenerator {
const cloudSqlInstances = [{ value: '', name: 'New Cloud SQL Instance' }];
shelljs.exec(
`gcloud sql instances list --format='value[separator=":"](project,region,name)' --project="${this.gcpProjectId}"`,
{ silent: true },
(code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.log.error(err);
} else {
_.forEach(stdout.toString().split(os.EOL), instance => {
Expand Down Expand Up @@ -508,7 +510,7 @@ module.exports = class extends BaseGenerator {
`gcloud sql databases list -i ${name} --format='value(name)' --project="${this.gcpProjectId}"`,
{ silent: true },
(code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.log.error(err);
} else {
_.forEach(stdout.toString().split(os.EOL), database => {
Expand Down Expand Up @@ -581,7 +583,7 @@ module.exports = class extends BaseGenerator {
`gcloud app create --region="${this.gaeLocation}" --project="${this.gcpProjectId}"`,
{ silent: true },
(code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.log.error(err);
this.abort = true;
}
Expand Down Expand Up @@ -612,15 +614,16 @@ module.exports = class extends BaseGenerator {
this.log(chalk.bold(`\n... Running: ${cmd}`));

shelljs.exec(cmd, { silent: true }, (code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.abort = true;
this.log.error(err);
}

this.gcpCloudSqlInstanceName = shelljs.exec(
const cloudSQLInstanceName = shelljs.exec(
`gcloud sql instances describe ${name} --format="value(connectionName)" --project="${this.gcpProjectId}"`,
{ silent: true }
);
this.gcpCloudSqlInstanceName = cloudSQLInstanceName.trim();

done();
});
Expand Down Expand Up @@ -648,7 +651,7 @@ module.exports = class extends BaseGenerator {
const cmd = `gcloud sql users create "${this.gcpCloudSqlUserName}" -i "${name}" --host="%" --password="${this.gcpCloudSqlPassword}" --project="${this.gcpProjectId}"`;
this.log(chalk.bold(`... Running: ${cmd}`));
shelljs.exec(cmd, { silent: true }, (code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.log.error(err);
}
done();
Expand All @@ -670,7 +673,7 @@ module.exports = class extends BaseGenerator {
const cmd = `gcloud sql databases create "${this.gcpCloudSqlDatabaseName}" --charset=utf8 -i "${name}" --project="${this.gcpProjectId}"`;
this.log(chalk.bold(`... Running: ${cmd}`));
shelljs.exec(cmd, { silent: true }, (code, stdout, err) => {
if (err) {
if (err && code !== 0) {
this.log.error(err);
}
done();
Expand Down Expand Up @@ -705,9 +708,7 @@ module.exports = class extends BaseGenerator {
this.log(chalk.bold('\nCreating Google App Engine deployment files'));

this.template('app.yaml.ejs', `${constants.MAIN_DIR}/appengine/app.yaml`);
if (this.gaeCloudSQLInstanceNeeded === 'Y') {
this.template('application-prod-gae.yml.ejs', `${constants.SERVER_MAIN_RES_DIR}/config/application-prod-gae.yml`);
}
this.template('application-prod-gae.yml.ejs', `${constants.SERVER_MAIN_RES_DIR}/config/application-prod-gae.yml`);
if (this.buildTool === 'gradle') {
this.template('gae.gradle.ejs', 'gradle/gae.gradle');
}
Expand All @@ -719,6 +720,9 @@ module.exports = class extends BaseGenerator {

addDependencies() {
if (this.abort) return;
if (this.buildTool === 'maven') {
this.addMavenDependency('org.springframework.boot.experimental', 'spring-boot-thin-layout', '1.0.23.RELEASE');
}
if (this.gaeCloudSQLInstanceNeeded === 'N') return;
if (this.prodDatabaseType === 'mysql' || this.prodDatabaseType === 'mariadb') {
if (this.buildTool === 'maven') {
Expand All @@ -739,24 +743,24 @@ module.exports = class extends BaseGenerator {
addGradlePlugin() {
if (this.abort) return;
if (this.buildTool === 'gradle') {
if (this.gaeCloudSQLInstanceNeeded === 'Y') {
this.addGradlePlugin('com.google.cloud.tools', 'appengine-gradle-plugin', '2.1.0');
}
this.addGradlePlugin('com.google.cloud.tools', 'appengine-gradle-plugin', '2.2.0');
this.addGradlePlugin('org.springframework.boot.experimental', 'spring-boot-thin-gradle-plugin', '1.0.13.RELEASE');
this.applyFromGradleScript('gradle/gae');
}
},

addMavenPlugin() {
if (this.abort) return;
if (this.buildTool === 'maven') {
if (this.gaeCloudSQLInstanceNeeded === 'Y') {
this.render('pom-plugin.xml.ejs', rendered => {
this.addMavenPlugin('com.google.cloud.tools', 'appengine-maven-plugin', '2.1.0', rendered.trim());
});
}
this.render('pom-plugin.xml.ejs', rendered => {
this.addMavenPlugin('com.google.cloud.tools', 'appengine-maven-plugin', '2.2.0', rendered.trim());
});
this.render('pom-profile.xml.ejs', rendered => {
this.addMavenProfile('prod-gae', ` ${rendered.trim()}`);
});
this.render('pom-gae-build-profile.xml.ejs', rendered => {
this.addMavenProfile('gae', ` ${rendered.trim()}`);
});
}
}
};
Expand All @@ -769,10 +773,16 @@ module.exports = class extends BaseGenerator {

if (this.buildTool === 'maven') {
this.log(chalk.bold('\nRun App Engine DevServer Locally: ./mvnw package appengine:run -DskipTests'));
this.log(chalk.bold('Deploy to App Engine: ./mvnw package appengine:deploy -DskipTests -Pprod,prod-gae'));
this.log(
chalk.bold('Deploy to App Engine: ./mvnw clean && ./mvnw package appengine:deploy -DskipTests -Pgae,prod-gae')
);
} else if (this.buildTool === 'gradle') {
this.log(chalk.bold('\nRun App Engine DevServer Locally: ./gradlew appengineRun'));
this.log(chalk.bold('Deploy to App Engine: ./gradlew appengineDeploy -Pprod -Pprod-gae'));
this.log(
chalk.bold(
'Deploy to App Engine: ./gradlew thinResolve -Pgae -Pprod-gae && ./gradlew appengineDeploy -Pgae -Pprod-gae'
)
);
}
/*
if (this.gcpSkipBuild || this.gcpDeployType === 'git') {
Expand Down
19 changes: 19 additions & 0 deletions generators/gae/templates/app.yaml.ejs
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
<%#
Copyright 2013-2019 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
http://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.
-%>
# ========================================================================
# App Engine Configuration.
# Full reference is available at:
Expand Down Expand Up @@ -28,3 +46,4 @@ automatic_scaling:
max_instances: <%= gaeMaxInstances %>
<%_ } _%>
<%_ } _%>
entrypoint: java -Dthin.root=. -jar <%= dasherizedBaseName %>-0.0.1-SNAPSHOT.jar
11 changes: 7 additions & 4 deletions generators/gae/templates/application-prod-gae.yml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,26 @@
# Full reference is available at:
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
# ===================================================================

server:
port: ${PORT}
<%_ if (gaeCloudSQLInstanceNeeded === 'Y') { _%>
spring:
<%_ if (prodDatabaseType === 'mysql' || prodDatabaseType === 'mariadb') { _%>
<%_ if (prodDatabaseType === 'mysql' || prodDatabaseType === 'mariadb') { _%>
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: "jdbc:mysql://google/<%=gcpCloudSqlDatabaseName%>?cloudSqlInstance=<%=gcpCloudSqlInstanceName%>&socketFactory=com.google.cloud.sql.mysql.SocketFactory&useSSL=false"
username: "<%=gcpCloudSqlUserName%>"
password: "<%=gcpCloudSqlPassword%>"
hikari:
maximumPoolSize: 8
<%_ } _%>
<%_ if (prodDatabaseType === 'postgresql') { _%>
<%_ } _%>
<%_ if (prodDatabaseType === 'postgresql') { _%>
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: "jdbc:postgresql://google/<%=gcpCloudSqlDatabaseName%>?cloudSqlInstance=<%=gcpCloudSqlInstanceName%>&socketFactory=com.google.cloud.sql.postgres.SocketFactory"
username: "<%=gcpCloudSqlUserName%>"
password: "<%=gcpCloudSqlPassword%>"
hikari:
maximumPoolSize: 8
<%_ } _%>
<%_ } _%>
13 changes: 8 additions & 5 deletions generators/gae/templates/gae.gradle.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,11 @@ apply plugin: "com.google.cloud.tools.appengine"

appengine {
deploy {
project = "<%=gcpProjectId%>"
projectId = "<%=gcpProjectId%>"
version = "1"
}
}

bootWar {
webXml = file("${project.rootDir}/<%= CLIENT_MAIN_SRC_DIR %>WEB-INF/web.xml")
}

processResources {
if (project.hasProperty("prod-gae")) {
filesMatching("**/application.yml") {
Expand All @@ -38,3 +34,10 @@ processResources {
}
}
}

task copyFolders(type: Copy) {
into "$buildDir/staged-app/"
from "$buildDir/thin/root/"
}

appengineStage.finalizedBy copyFolders
61 changes: 61 additions & 0 deletions generators/gae/templates/pom-gae-build-profile.xml.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<%#
Copyright 2013-2019 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
http://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.
-%>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<!-- package as a thin jar -->
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.23.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-maven-plugin</artifactId>
<version>1.0.23.RELEASE</version>
<configuration>
<outputDirectory>${project.build.directory}/appengine-staging</outputDirectory>
</configuration>
<executions>
<execution>
<id>resolve</id>
<goals>
<goal>resolve</goal>
</goals>
<inherited>false</inherited>
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
</plugin>
</plugins>
</build>
20 changes: 19 additions & 1 deletion generators/gae/templates/pom-plugin.xml.ejs
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
<%#
Copyright 2013-2019 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
http://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.
-%>
<configuration>
<deploy.project><%=gcpProjectId%></deploy.project>
<deploy.projectId><%=gcpProjectId%></deploy.projectId>
<deploy.version>1</deploy.version>
</configuration>
Loading

0 comments on commit 7d21a02

Please sign in to comment.