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

initial support for Heroku deployment #85

Merged
merged 21 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from 15 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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ $ jhipster --blueprints micronaut
* Maven or Gradle Build System
* Angular or React Client
* Protractor Tests
* Heroku Deployment

## ❤️ for community

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,7 @@ export class LoginService {
logout(): void {
<%_ if (authenticationType === 'oauth2') { _%>
this.authServerProvider.logout().subscribe((logout: Logout) => {
let logoutUrl = logout.logoutUrl;
const redirectUri = `${location.origin}${this.location.prepareExternalUrl('/')}`;

// if Keycloak, uri has protocol/openid-connect/token
if (logoutUrl.includes('/protocol')) {
logoutUrl = logoutUrl + '?redirect_uri=' + redirectUri;
} else {
// Okta
logoutUrl = logoutUrl + '?id_token_hint=' +
logout.idToken + '&post_logout_redirect_uri=' + redirectUri;
}
window.location.href = logoutUrl;
window.location.href = logout.logoutUrl;
});
<%_ } else { _%>
this.authServerProvider.logout().subscribe(null, null, () => this.accountService.authenticate(null));
Expand Down
158 changes: 158 additions & 0 deletions generators/heroku/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/* eslint-disable consistent-return */
const chalk = require('chalk');
const fs = require('fs');
const ChildProcess = require('child_process');
const util = require('util');
const os = require('os');
const HerokuGenerator = require('generator-jhipster/generators/heroku');
const constants = require('generator-jhipster/generators/generator-constants');
const { getBase64Secret } = require('generator-jhipster/generators/utils');

const execCmd = util.promisify(ChildProcess.exec);

module.exports = class extends HerokuGenerator {
constructor(args, opts) {
super(args, { fromBlueprint: true, ...opts }); // fromBlueprint variable is important

const jhContext = (this.jhipsterContext = this.options.jhipsterContext);

if (!jhContext) {
this.error(`This is a JHipster blueprint and should be used only like ${chalk.yellow('jhipster --blueprint micronaut')}`);
}

this.configOptions = jhContext.configOptions || {};
}

/**
* build a generated application.
*
* @param {String} buildTool - maven | gradle
* @param {String} profile - dev | prod
* @param {Boolean} buildWar - build a war instead of a jar
* @param {Function} cb - callback when build is complete
* @returns {object} the command line and its result
*/
buildApplication(buildTool, profile, buildWar, cb) {
atomfrede marked this conversation as resolved.
Show resolved Hide resolved
let buildCmd = 'mvnw -ntp verify -B';

if (buildTool === 'gradle') {
buildCmd = 'gradlew shadowJar';
}

if (os.platform() !== 'win32') {
buildCmd = `./${buildCmd}`;
}
buildCmd += ` -P${profile}`;
return {
stdout: ChildProcess.exec(buildCmd, { maxBuffer: 1024 * 10000 }, cb).stdout,
buildCmd,
};
}

get initializing() {
return {
validateFromCli() {
this.checkInvocationFromCLI();
},

async checkInstallation() {
try {
await execCmd('heroku --version');
} catch (err) {
this.log.error("You don't have the Heroku CLI installed. Download it from https://cli.heroku.com/");
}
},

initializing() {
this.log(chalk.bold('Heroku configuration is starting'));
const configuration = this.getAllJhipsterConfig(this, true);
this.env.options.appPath = configuration.get('appPath') || constants.CLIENT_MAIN_SRC_DIR;
this.baseName = configuration.get('baseName');
this.packageName = configuration.get('packageName');
this.packageFolder = configuration.get('packageFolder');
this.cacheProvider = configuration.get('cacheProvider') || configuration.get('hibernateCache') || 'no';
this.enableHibernateCache = configuration.get('enableHibernateCache') && !['no', 'memcached'].includes(this.cacheProvider);
this.databaseType = configuration.get('databaseType');
this.prodDatabaseType = configuration.get('prodDatabaseType');
this.searchEngine = configuration.get('searchEngine');
this.angularAppName = this.getAngularAppName();
this.buildTool = configuration.get('buildTool');
this.applicationType = configuration.get('applicationType');
this.reactive = configuration.get('reactive') || false;
this.serviceDiscoveryType = configuration.get('serviceDiscoveryType');
this.authenticationType = configuration.get('authenticationType');
this.herokuAppName = configuration.get('herokuAppName');
this.dynoSize = 'Free';
this.herokuDeployType = configuration.get('herokuDeployType');
this.herokuJavaVersion = configuration.get('herokuJavaVersion');
this.useOkta = configuration.get('useOkta');
this.oktaAdminLogin = configuration.get('oktaAdminLogin');
this.oktaAdminPassword = configuration.get('oktaAdminPassword');
},
};
}

get prompting() {
return super._prompting();
}

get configuring() {
return super._configuring();
}

get default() {
const phaseFromJHipster = super._default();
const jhipsterMicronautDefaultPhaseSteps = {
copyHerokuFiles() {
if (this.abort) return;

const done = this.async();
this.log(chalk.bold('\nCreating Heroku deployment files'));

this.template('application-heroku.yml.ejs', `${constants.SERVER_MAIN_RES_DIR}/application-heroku.yml`);
this.template('Procfile.ejs', 'Procfile');
this.template('system.properties.ejs', 'system.properties');
this.template(
'SSLEnforcingHostResolver.java.ejs',
`${constants.SERVER_MAIN_SRC_DIR}${this.packageFolder}/config/SSLEnforcingHostResolver.java`
);
if (this.buildTool === 'gradle') {
this.template('heroku.gradle.ejs', 'gradle/heroku.gradle');
}
if (this.useOkta) {
this.template('provision-okta-addon.sh.ejs', 'provision-okta-addon.sh');
fs.appendFile('.gitignore', 'provision-okta-addon.sh', 'utf8', (err, data) => {
this.log(`${chalk.yellow.bold('WARNING!')}Failed to add 'provision-okta-addon.sh' to .gitignore.'`);
});
}

done();
},
addHerokuDependencies() {
// Nothing to do here right now
},
addJwtSecretEnvironmentVariable() {
const done = this.async();
const jwtSecret = getBase64Secret(null, 64);
const setJwtSecretCommand = `heroku config:set JWT_SECRET=${jwtSecret} --app ${this.herokuAppName}`;

const child = ChildProcess.exec(setJwtSecretCommand, (err, stdout, stderr) => {
if (err) {
this.abort = true;
this.log.error(err);
}
done();
});

child.stdout.on('data', data => {
this.log(data.toString());
});
},
};
return Object.assign(phaseFromJHipster, jhipsterMicronautDefaultPhaseSteps);
}

get end() {
return super._end();
}
};
22 changes: 22 additions & 0 deletions generators/heroku/templates/Procfile.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<%#
Copyright 2013-2020 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.
-%>
web: java $JAVA_OPTS <% if (applicationType === 'gateway' || dynoSize === 'Free') { %>-Xmx128m<% } %> -Dmicronaut.env.deduction=false -Dmicronaut.environments=prod,cloud<% if (buildTool == 'maven' && herokuDeployType == 'git') { %>,no-liquibase<% } %>,heroku -Dmicronaut.server.port=$port -jar <% if (buildTool === 'maven') { %>target/*.jar<% } %><% if (buildTool === 'gradle') { %>build/libs/*-all.jar<% } %>
<%_ if (buildTool == 'maven' && herokuDeployType == 'git' && (prodDatabaseType === 'postgresql' || prodDatabaseType === 'mysql' || prodDatabaseType === 'mariadb')) { _%>
release: cp -R src/main/resources/config config && ./mvnw -ntp liquibase:update -Pprod,heroku
<%_ } _%>
37 changes: 37 additions & 0 deletions generators/heroku/templates/SSLEnforcingHostResolver.java.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package <%=packageName%>.config;

import io.micronaut.context.annotation.Replaces;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.server.HttpServerConfiguration;
import io.micronaut.http.server.util.DefaultHttpHostResolver;
import io.micronaut.runtime.server.EmbeddedServer;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Provider;
import javax.inject.Singleton;

@Replaces(DefaultHttpHostResolver.class)
@Singleton
@Requires(env = Environment.HEROKU)
public class SSLEnforcingHostResolver extends DefaultHttpHostResolver {

public static final String HTTP = "http://";
public static final String HTTPS = "https://";

public SSLEnforcingHostResolver(HttpServerConfiguration serverConfiguration, Provider<EmbeddedServer> embeddedServer) {
super(serverConfiguration, embeddedServer);
}

@Nonnull
@Override
public String resolve(@Nullable HttpRequest request) {
String host = super.resolve(request);
if (host.startsWith(HTTP)) {
return host.replace(HTTP, HTTPS);
}
return host;
}
}
63 changes: 63 additions & 0 deletions generators/heroku/templates/application-heroku.yml.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<%#
Copyright 2013-2020 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.
-%>
# ===================================================================
# Micronaut configuration for the "heroku" profile.
#
# This configuration overrides the application.yml file.
# ===================================================================

# ===================================================================
# Standard Micronaut configuration properties.
# Full reference is available at:
# https://docs.micronaut.io/latest/guide/configurationreference.html
# ===================================================================


micronaut:
security:
token:
jwt:
signatures:
secret:
generator:
secret: ${JWT_SECRET}
<%_ if (useOkta) { _%>
oauth2:
enabled: true
clients:
oidc:
client-secret: ${OKTA_OAUTH2_CLIENT_SECRET_WEB}
client-id: ${OKTA_OAUTH2_CLIENT_ID_WEB}
openid:
issuer: ${OKTA_OAUTH2_ISSUER}
end-session:
enabled: true
<%_ } _%>

datasources:
default:
<%_ if (prodDatabaseType === 'postgresql' || prodDatabaseType === 'mysql' || prodDatabaseType === 'mariadb') { _%>
type: com.zaxxer.hikari.HikariDataSource
url: ${JDBC_DATABASE_URL}
username: ${JDBC_DATABASE_USERNAME}
password: ${JDBC_DATABASE_PASSWORD}
hikari:
maximum-pool-size: 8
<%_ } _%>

41 changes: 41 additions & 0 deletions generators/heroku/templates/heroku.gradle.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<%#
Copyright 2013-2020 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.
-%>
apply plugin: "com.heroku.sdk.heroku-gradle"

heroku {
appName = "<%= herokuAppName %>"
buildpacks = ["heroku/jvm"]
}
<%_ if (herokuDeployType === 'git') { _%>

// Task stage is used by Heroku, see also
// https://devcenter.heroku.com/articles/deploying-gradle-apps-on-heroku
// and GRADLE_TASK configuration variable.
task stage(dependsOn: "shadowJar") {
}

gradle.taskGraph.whenReady {taskGraph ->
taskGraph.afterTask() {task ->
if (task.getName().equals("stage") && System.getenv("DYNO") != null) {
delete "node_modules"
delete fileTree(dir: "build/libs", exclude: "*.jar")
}
}
}
<%_ } _%>
Loading