diff --git a/README.md b/README.md index ad9e0675ce4..a1ea6a9df26 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Liquibase system requirements can be found on the [Download Liquibase](https://w 2. Make sure to add Liquibase to your PATH. 3. Copy the included `examples` directory to the needed location. 4. Open your CLI and navigate to your `examples/sql` or `examples/xml` directory. -5. Start the included H2 database with the `./start-h2` command. +5. Start the included H2 database with the `liquibase init start-h2` command. 6. Run the `liquibase update` command. 7. Optionally, follow the prompt for your email to register for [Liquibase Hub](https://hub.liquibase.com/). 8. Run the `liquibase history` command. diff --git a/liquibase-core/src/main/java/liquibase/command/core/StartH2CommandStep.java b/liquibase-core/src/main/java/liquibase/command/core/StartH2CommandStep.java new file mode 100644 index 00000000000..b9dc00ec1d0 --- /dev/null +++ b/liquibase-core/src/main/java/liquibase/command/core/StartH2CommandStep.java @@ -0,0 +1,169 @@ +package liquibase.command.core; + +import liquibase.Scope; +import liquibase.command.*; +import liquibase.configuration.ConfigurationValueObfuscator; + +import java.sql.Connection; +import java.sql.DriverManager; + +public class StartH2CommandStep extends AbstractCommandStep { + + public static final String[] COMMAND_NAME = {"init", "startH2"}; + + public static final CommandArgumentDefinition BIND_ARG; + public static final CommandArgumentDefinition DB_PORT_ARG; + public static final CommandArgumentDefinition WEB_PORT_ARG; + public static final CommandArgumentDefinition USERNAME_ARG; + public static final CommandArgumentDefinition PASSWORD_ARG; + public static final CommandArgumentDefinition LAUNCH_BROWSER_ARG; + + static { + CommandBuilder builder = new CommandBuilder(COMMAND_NAME); + DB_PORT_ARG = builder.argument("dbPort", Integer.class) + .description("Port to run h2 database on") + .defaultValue(9090) + .build(); + + WEB_PORT_ARG = builder.argument("webPort", Integer.class) + .description("Port to run h2's web interface on") + .defaultValue(8080) + .build(); + + USERNAME_ARG = builder.argument("username", String.class) + .description("Username to create in h2") + .defaultValue("dbuser") + .build(); + + PASSWORD_ARG = builder.argument("password", String.class) + .description("Password to use for created h2 user") + .defaultValue("letmein") + .setValueObfuscator(ConfigurationValueObfuscator.STANDARD) + .build(); + + BIND_ARG = builder.argument("bindAddress", String.class) + .description("Network address to bind to") + .defaultValue("127.0.0.1") + .build(); + + LAUNCH_BROWSER_ARG = builder.argument("launchBrowser", Boolean.class) + .description("Whether to open a browser to the database's web interface") + .defaultValue(true) + .build(); + } + + @Override + public void run(CommandResultsBuilder resultsBuilder) throws Exception { + final CommandScope commandScope = resultsBuilder.getCommandScope(); + + System.setProperty("h2.bindAddress", commandScope.getConfiguredValue(BIND_ARG).getValue()); + + System.out.println("Starting Example H2 Database..."); + System.out.println("NOTE: The database does not persist data, so stopping and restarting this process will reset it back to a blank database"); + System.out.println(); + + try { + Class.forName("org.h2.Driver"); + } catch (ClassNotFoundException e) { + String msg = "ERROR: H2 was not configured properly. To use Liquibase and H2, you need to have the H2 JDBC driver jar file in liquibase/lib. Learn more at https://docs.liquibase.com/"; + System.out.println(msg); + throw e; + } + + final String username = commandScope.getConfiguredValue(USERNAME_ARG).getValue(); + final String password = commandScope.getConfiguredValue(PASSWORD_ARG).getValue(); + final Integer dbPort = commandScope.getConfiguredValue(DB_PORT_ARG).getValue(); + final Integer webPort = commandScope.getConfiguredValue(WEB_PORT_ARG).getValue(); + + try (Connection devConnection = DriverManager.getConnection("jdbc:h2:mem:dev", username, password); + Connection intConnection = DriverManager.getConnection("jdbc:h2:mem:integration", username, password)) { + + startTcpServer(dbPort); + + Object webServer = startWebServer(webPort); + String devUrl = createWebSession(devConnection, webServer, commandScope.getConfiguredValue(LAUNCH_BROWSER_ARG).getValue()); + String intUrl = createWebSession(intConnection, webServer, false); + + System.out.println("Connection Information:" + System.lineSeparator() + + " Dev database: " + System.lineSeparator() + + " JDBC URL: jdbc:h2:tcp://localhost:" + dbPort + "/mem:dev" + System.lineSeparator() + + " Username: " + username + System.lineSeparator() + + " Password: " + password + System.lineSeparator() + + " Integration database: " + System.lineSeparator() + + " JDBC URL: jdbc:h2:tcp://localhost:" + dbPort + "/mem:integration" + System.lineSeparator() + + " Username: " + username + System.lineSeparator() + + " Password: " + password + System.lineSeparator() + + "" + System.lineSeparator() + + "Opening Database Console in Browser..." + System.lineSeparator() + + " Dev Web URL: " + devUrl + System.lineSeparator() + + " Integration Web URL: " + intUrl + System.lineSeparator()); + + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + Scope.getCurrentScope().getUI().sendMessage("Shutting down H2 database..."); + })); + + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException e) { + throw e; + } catch (Throwable e) { + e.printStackTrace(); + System.exit(-1); + } + + resultsBuilder.addResult("statusCode", 0); + } + + @Override + public String[][] defineCommandNames() { + return new String[][]{COMMAND_NAME}; + } + + @Override + public void adjustCommandDefinition(CommandDefinition commandDefinition) { + super.adjustCommandDefinition(commandDefinition); + commandDefinition.setShortDescription( + "Launches H2, an included open source in-memory database. This Java application is shipped with Liquibase, and is useful in the Getting Started experience and for testing out Liquibase commands."); + commandDefinition.setGroupShortDescription(new String[]{"init"}, "Init commands"); + } + + protected static void startTcpServer(Integer dbPort) throws Exception { + final Class serverClass = Class.forName("org.h2.tools.Server"); + final Object tcpServer = serverClass.getMethod("createTcpServer", String[].class) + .invoke(null, (Object) new String[]{"-tcpAllowOthers", "-tcpPort", dbPort.toString()}); + + tcpServer.getClass().getMethod("start") + .invoke(tcpServer); + } + + protected static Object startWebServer(Integer webPort) throws Exception { + final Class serverClass = Class.forName("org.h2.tools.Server"); + + final Object webServer = Class.forName("org.h2.server.web.WebServer").newInstance(); + Object web = serverClass.getConstructor(Class.forName("org.h2.server.Service"), String[].class).newInstance(webServer, (Object) new String[]{"-webPort", webPort.toString()}); + web.getClass().getMethod("start").invoke(web); + + return webServer; + } + + private static String createWebSession(Connection connection, Object webServer, boolean openBrowser) throws Exception { + final Class serverClass = Class.forName("org.h2.tools.Server"); + + String url = (String) webServer.getClass().getMethod("addSession", Connection.class).invoke(webServer, connection); + + if (openBrowser) { + try { + serverClass.getMethod("openBrowser", String.class).invoke(null, url); + } catch (Exception e) { + String message = e.getMessage(); + if (message == null && e.getCause() != null) { + message = e.getCause().getMessage(); + } + System.out.println("Cannot open browser: "+ message); + System.out.println(""); + } + } + + return url; + } +} diff --git a/liquibase-core/src/main/java/liquibase/example/StartH2Main.java b/liquibase-core/src/main/java/liquibase/example/StartH2Main.java deleted file mode 100644 index 950cff36d13..00000000000 --- a/liquibase-core/src/main/java/liquibase/example/StartH2Main.java +++ /dev/null @@ -1,112 +0,0 @@ -package liquibase.example; - -import java.lang.reflect.InvocationTargetException; -import java.sql.Connection; -import java.sql.DriverManager; -import java.util.concurrent.TimeUnit; - -/** - * Wrapper around the h2 console for use in the "examples" directory - */ -@SuppressWarnings("java:S2189") -public class StartH2Main { - - private static final String dbPort = "9090"; - private static final String webPort = "8090"; - private static final String username = "dbuser"; - private static final String password = "letmein"; - - public static void main(String[] args) throws Exception { - System.out.println("Starting Example H2 Database..."); - System.out.println("NOTE: The database does not persist data, so stopping and restarting this process will reset it back to a blank database"); - System.out.println(); - - try { - Class.forName("org.h2.Driver"); - } catch (ClassNotFoundException e) { - String msg = "ERROR: H2 was not configured properly. To use Liquibase and H2, you need to have the JDBC driver .jar file. Liquibase includes the H2 in-memory database and the h2-.jar file in liquibase/lib. Learn more at https://docs.liquibase.com/"; - System.out.println(msg); - throw e; - } - - try (Connection devConnection = DriverManager.getConnection("jdbc:h2:mem:dev", username, password); - Connection intConnection = DriverManager.getConnection("jdbc:h2:mem:integration", username, password)) { - - startTcpServer(); - - Object webServer = startWebServer(); - String devUrl = createWebSession(devConnection, webServer, true); - String intUrl = createWebSession(intConnection, webServer, false); - - System.out.println("Connection Information:" + System.lineSeparator() + - " Dev database: " + System.lineSeparator() + - " JDBC URL: jdbc:h2:tcp://localhost:" + dbPort + "/mem:dev" + System.lineSeparator() + - " Username: " + username + System.lineSeparator() + - " Password: " + password + System.lineSeparator() + - " Integration database: " + System.lineSeparator() + - " JDBC URL: jdbc:h2:tcp://localhost:" + dbPort + "/mem:integration" + System.lineSeparator() + - " Username: " + username + System.lineSeparator() + - " Password: " + password + System.lineSeparator() + - "" + System.lineSeparator() + - "Opening Database Console in Browser..." + System.lineSeparator() + - " Dev Web URL: " + devUrl + System.lineSeparator() + - " Integration Web URL: " + intUrl + System.lineSeparator()); - - - while (true) { - try { - Thread.sleep(TimeUnit.SECONDS.toMillis(60)); - } catch (InterruptedException interruptedException) { - Thread.currentThread().interrupt(); - } - } - - } catch (Throwable e) { - e.printStackTrace(); - System.exit(-1); - } - - - } - - protected static void startTcpServer() throws Exception { - final Class serverClass = Class.forName("org.h2.tools.Server"); - final Object tcpServer = serverClass.getMethod("createTcpServer", String[].class) - .invoke(null, (Object) new String[]{"-tcpAllowOthers", "-tcpPort", dbPort}); - - tcpServer.getClass().getMethod("start") - .invoke(tcpServer); - } - - protected static Object startWebServer() throws Exception { - final Class serverClass = Class.forName("org.h2.tools.Server"); - - final Object webServer = Class.forName("org.h2.server.web.WebServer").newInstance(); - Object web = serverClass.getConstructor(Class.forName("org.h2.server.Service"), String[].class).newInstance(webServer, (Object) new String[]{"-webPort", webPort}); - web.getClass().getMethod("start").invoke(web); - - return webServer; - } - - private static String createWebSession(Connection connection, Object webServer, boolean openBrowser) throws Exception { - final Class serverClass = Class.forName("org.h2.tools.Server"); - - String url = (String) webServer.getClass().getMethod("addSession", Connection.class).invoke(webServer, connection); - - if (openBrowser) { - try { - serverClass.getMethod("openBrowser", String.class).invoke(null, url); - } catch (Exception e) { - String message = e.getMessage(); - if (message == null && e.getCause() != null) { - message = e.getCause().getMessage(); - } - System.out.println("Cannot open browser: "+ message); - System.out.println(""); - } - } - - return url; - } - -} \ No newline at end of file diff --git a/liquibase-core/src/main/resources/META-INF/services/liquibase.command.CommandStep b/liquibase-core/src/main/resources/META-INF/services/liquibase.command.CommandStep index 00512a4493d..bbe41212c38 100644 --- a/liquibase-core/src/main/resources/META-INF/services/liquibase.command.CommandStep +++ b/liquibase-core/src/main/resources/META-INF/services/liquibase.command.CommandStep @@ -36,6 +36,7 @@ liquibase.command.core.RollbackToDateCommandStep liquibase.command.core.RollbackToDateSqlCommandStep liquibase.command.core.SnapshotCommandStep liquibase.command.core.SnapshotReferenceCommandStep +liquibase.command.core.StartH2CommandStep liquibase.command.core.StatusCommandStep liquibase.command.core.SyncHubCommandStep liquibase.command.core.TagCommandStep diff --git a/liquibase-core/src/main/resources/liquibase/examples/start-h2 b/liquibase-core/src/main/resources/liquibase/examples/start-h2 index f03fb9ade28..4b5a96f5e59 100644 --- a/liquibase-core/src/main/resources/liquibase/examples/start-h2 +++ b/liquibase-core/src/main/resources/liquibase/examples/start-h2 @@ -13,29 +13,4 @@ if [ -z "${LIQUIBASE_HOME}" ]; then LIQUIBASE_HOME=$(dirname "$(which liquibase)") fi -if [ -z "${JAVA_HOME}" ]; then - #JAVA_HOME not set, try to find a bundled version - if [ -d "${LIQUIBASE_HOME}/jre" ]; then - JAVA_HOME="$LIQUIBASE_HOME/jre" - elif [ -d "${LIQUIBASE_HOME}/.install4j/jre.bundle/Contents/Home" ]; then - JAVA_HOME="${LIQUIBASE_HOME}/.install4j/jre.bundle/Contents/Home" - fi -fi - -if [ -z "${JAVA_HOME}" ]; then - JAVA_PATH="$(which java)" - - if [ -z "${JAVA_PATH}" ]; then - echo "Cannot find java in your path. Install java or use the JAVA_HOME environment variable" - - exit 1 - fi -else - #Use path in JAVA_HOME - JAVA_PATH="${JAVA_HOME}/bin/java" -fi - - -# echo "${JAVA_PATH}" -cp "${LIQUIBASE_HOME}/lib/h2-1.4.200.jar:${LIQUIBASE_HOME}/liquibase.jar" liquibase.example.StartH2Main - -"${JAVA_PATH}" -cp "${LIQUIBASE_HOME}/lib/h2-2.1.212.jar:${LIQUIBASE_HOME}/liquibase.jar" liquibase.example.StartH2Main +"${LIQUIBASE_HOME}/liquibase" init start-h2 diff --git a/liquibase-core/src/main/resources/liquibase/examples/start-h2.bat b/liquibase-core/src/main/resources/liquibase/examples/start-h2.bat index cea714b69ed..ce3cb5aa5b1 100644 --- a/liquibase-core/src/main/resources/liquibase/examples/start-h2.bat +++ b/liquibase-core/src/main/resources/liquibase/examples/start-h2.bat @@ -6,7 +6,7 @@ setlocal enabledelayedexpansion rem %~dp0 is expanded pathname of the current script under NT rem %~p0 is the directory of the current script -if exist %~p0\..\liquibase.jar SET LIQUIBASE_HOME="%~p0\.." +if exist %~p0\..\liquibase.jar SET LIQUIBASE_HOME=%~p0.. if "%LIQUIBASE_HOME%"=="" ( FOR /F "tokens=* USEBACKQ" %%g IN (`where liquibase.bat`) do (SET "LIQUIBASE_HOME=%%~dpg") @@ -17,18 +17,4 @@ if "%LIQUIBASE_HOME%"=="" ( exit /B 1 ) -if "%JAVA_HOME%"=="" ( - - rem check for jre dir in liquibase_home - if NOT "%LIQUIBASE_HOME%"=="" if exist "%LIQUIBASE_HOME%\jre" ( - set JAVA_HOME=%LIQUIBASE_HOME%\jre - ) -) - -if "%JAVA_HOME%"=="" ( - set JAVA_PATH=java -) else ( - set JAVA_PATH=%JAVA_HOME%\bin\java -) - -"%JAVA_PATH%" -cp "%LIQUIBASE_HOME%\lib\h2-2.1.212.jar;%LIQUIBASE_HOME%\liquibase.jar" liquibase.example.StartH2Main +"%LIQUIBASE_HOME%\liquibase.bat" init start-h2 diff --git a/liquibase-dist/src/main/archive/GETTING_STARTED.txt b/liquibase-dist/src/main/archive/GETTING_STARTED.txt index 71a8f1ab7de..1edfaaa3218 100644 --- a/liquibase-dist/src/main/archive/GETTING_STARTED.txt +++ b/liquibase-dist/src/main/archive/GETTING_STARTED.txt @@ -77,15 +77,14 @@ complex database. To start the example database: * open your command line or Terminal app - * navigate to the directory where you placed "examples" directory - * run `examples/start-h2`. + * run `liquibase init start-h2`. To stop the example database: - * run `ctrl-c' in the terminal running examples/start-h2 + * run `ctrl-c' in the terminal running `liquibase init start-h2` Notes -Running `examples/start-h2` starts local H2 databases that listen on port 9090, and +Running `liquibase init start-h2` starts local H2 databases that listen on port 9090, and it opens a browser to the database console on the same port. * The Example h2 databases do not store data and will reset when the start-h2 process ends via "ctrl-c". diff --git a/liquibase-dist/src/main/archive/examples/start-h2 b/liquibase-dist/src/main/archive/examples/start-h2 deleted file mode 100644 index c40b3722f2b..00000000000 --- a/liquibase-dist/src/main/archive/examples/start-h2 +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -if [ -z "${LIQUIBASE_HOME}" ]; then - #liquibase home is not set - - LIQUIBASE_PATH="$(which liquibase)" - - if [ -z "${LIQUIBASE_PATH}" ]; then - echo "Must set LIQUIBASE_HOME environment variable, or have liquibase in your PATH" - exit 1 - fi - - LIQUIBASE_HOME=$(dirname "$(which liquibase)") -fi - -if [ -z "${JAVA_HOME}" ]; then - #JAVA_HOME not set, try to find a bundled version - if [ -d "${LIQUIBASE_HOME}/jre" ]; then - JAVA_HOME="$LIQUIBASE_HOME/jre" - elif [ -d "${LIQUIBASE_HOME}/.install4j/jre.bundle/Contents/Home" ]; then - JAVA_HOME="${LIQUIBASE_HOME}/.install4j/jre.bundle/Contents/Home" - fi -fi - -if [ -z "${JAVA_HOME}" ]; then - JAVA_PATH="$(which java)" - - if [ -z "${JAVA_PATH}" ]; then - echo "Cannot find java in your path. Install java or use the JAVA_HOME environment variable" - - exit 1 - fi -else - #Use path in JAVA_HOME - JAVA_PATH="${JAVA_HOME}/bin/java" -fi - -"${JAVA_PATH}" -cp "${LIQUIBASE_HOME}/lib/h2-2.1.212.jar:${LIQUIBASE_HOME}/liquibase.jar" liquibase.example.StartH2Main diff --git a/liquibase-dist/src/main/archive/examples/start-h2.bat b/liquibase-dist/src/main/archive/examples/start-h2.bat deleted file mode 100644 index cea714b69ed..00000000000 --- a/liquibase-dist/src/main/archive/examples/start-h2.bat +++ /dev/null @@ -1,34 +0,0 @@ -@echo off -if "%OS%" == "Windows_NT" setlocal - -setlocal enabledelayedexpansion - -rem %~dp0 is expanded pathname of the current script under NT -rem %~p0 is the directory of the current script - -if exist %~p0\..\liquibase.jar SET LIQUIBASE_HOME="%~p0\.." - -if "%LIQUIBASE_HOME%"=="" ( - FOR /F "tokens=* USEBACKQ" %%g IN (`where liquibase.bat`) do (SET "LIQUIBASE_HOME=%%~dpg") -) - -if "%LIQUIBASE_HOME%"=="" ( - echo "Must set LIQUIBASE_HOME environment variable, or have liquibase.bat in your PATH" - exit /B 1 -) - -if "%JAVA_HOME%"=="" ( - - rem check for jre dir in liquibase_home - if NOT "%LIQUIBASE_HOME%"=="" if exist "%LIQUIBASE_HOME%\jre" ( - set JAVA_HOME=%LIQUIBASE_HOME%\jre - ) -) - -if "%JAVA_HOME%"=="" ( - set JAVA_PATH=java -) else ( - set JAVA_PATH=%JAVA_HOME%\bin\java -) - -"%JAVA_PATH%" -cp "%LIQUIBASE_HOME%\lib\h2-2.1.212.jar;%LIQUIBASE_HOME%\liquibase.jar" liquibase.example.StartH2Main diff --git a/liquibase-dist/src/main/assembly/assembly-bin.xml b/liquibase-dist/src/main/assembly/assembly-bin.xml index 9697fb1b71e..58a216d23e8 100644 --- a/liquibase-dist/src/main/assembly/assembly-bin.xml +++ b/liquibase-dist/src/main/assembly/assembly-bin.xml @@ -13,7 +13,6 @@ unix liquibase - examples/start-h2 0755 @@ -51,6 +50,26 @@ **/* + + **/start-h2* + + + + ${project.basedir}/../liquibase-core/target/classes/liquibase/examples + examples + + **/start-h2 + + unix + 0755 + + + ${project.basedir}/../liquibase-core/target/classes/liquibase/examples + examples + + **/start-h2.bat + + dos diff --git a/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/initstartH2.test.groovy b/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/initstartH2.test.groovy new file mode 100644 index 00000000000..30e952121ee --- /dev/null +++ b/liquibase-integration-tests/src/test/resources/liquibase/extension/testing/command/initstartH2.test.groovy @@ -0,0 +1,27 @@ +package liquibase.extension.testing.command + +CommandTests.define { + + command = ["init", "startH2"] + + signature = """ +Short Description: Launches H2, an included open source in-memory database. This Java application is shipped with Liquibase, and is useful in the Getting Started experience and for testing out Liquibase commands. +Long Description: NOT SET +Required Args: + NONE +Optional Args: + bindAddress (String) Network address to bind to + Default: 127.0.0.1 + dbPort (Integer) Port to run h2 database on + Default: 9090 + launchBrowser (Boolean) Whether to open a browser to the database's web interface + Default: true + password (String) Password to use for created h2 user + Default: letmein + OBFUSCATED + username (String) Username to create in h2 + Default: dbuser + webPort (Integer) Port to run h2's web interface on + Default: 8080 +""" +}