Skip to content

Commit

Permalink
DAT-8615: init hub command (#2326)
Browse files Browse the repository at this point in the history
Co-authored-by: Wesley Willard <wwillard@datical.com>
Co-authored-by: Nathan Voxland <nathan@voxland.net>
Co-authored-by: Wesley willard <wwillard@austin.rr.com>
Co-authored-by: Surya Aki <saki@datical.com>
Co-authored-by: suryaaki2 <80348493+suryaaki2@users.noreply.github.com>
Co-authored-by: Wesley Willard <wwillard@liquibase.com>
  • Loading branch information
7 people committed Feb 18, 2022
1 parent 6cdea2e commit 0bb2eae
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 100 deletions.
Expand Up @@ -5,6 +5,7 @@
import liquibase.changelog.ChangelogRewriter;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.command.*;
import liquibase.exception.ChangeLogAlreadyRegisteredException;
import liquibase.exception.CommandExecutionException;
import liquibase.exception.CommandLineParsingException;
import liquibase.exception.LiquibaseException;
Expand All @@ -21,6 +22,7 @@
import liquibase.util.StringUtil;

import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.*;

public class RegisterChangelogCommandStep extends AbstractCommandStep {
Expand Down Expand Up @@ -52,49 +54,52 @@ public String[][] defineCommandNames() {

@Override
public void run(CommandResultsBuilder resultsBuilder) throws Exception {
try (PrintWriter output = new PrintWriter(resultsBuilder.getOutputStream())) {
CommandScope commandScope = resultsBuilder.getCommandScope();

final UIService ui = Scope.getCurrentScope().getUI();
CommandScope commandScope = resultsBuilder.getCommandScope();
//
// Access the HubService
// Stop if we do no have a key
//
final HubServiceFactory hubServiceFactory = Scope.getCurrentScope().getSingleton(HubServiceFactory.class);
if (!hubServiceFactory.isOnline()) {
throw new CommandExecutionException("The command registerChangeLog requires communication with Liquibase Hub, \nwhich is prevented by liquibase.hub.mode='off'. \nPlease set to 'all' or 'meta' and try again. \nLearn more at https://hub.liquibase.com");
}

//
// Access the HubService
// Stop if we do no have a key
//
final HubServiceFactory hubServiceFactory = Scope.getCurrentScope().getSingleton(HubServiceFactory.class);
if (!hubServiceFactory.isOnline()) {
throw new CommandExecutionException("The command registerChangeLog requires communication with Liquibase Hub, \nwhich is prevented by liquibase.hub.mode='off'. \nPlease set to 'all' or 'meta' and try again. \nLearn more at https://hub.liquibase.com");
}
//
// Check for existing changeLog file
//
String changeLogFile = commandScope.getArgumentValue(CHANGELOG_FILE_ARG);
UUID hubProjectId = commandScope.getArgumentValue(HUB_PROJECT_ID_ARG);
String hubProjectName = commandScope.getArgumentValue(HUB_PROJECT_NAME_ARG);

//
// Check for existing changeLog file
//
final HubService service = Scope.getCurrentScope().getSingleton(HubServiceFactory.class).getService();
HubChangeLog hubChangeLog;
String changeLogFile = commandScope.getArgumentValue(CHANGELOG_FILE_ARG);
UUID hubProjectId = commandScope.getArgumentValue(HUB_PROJECT_ID_ARG);
String hubProjectName = commandScope.getArgumentValue(HUB_PROJECT_NAME_ARG);
doRegisterChangelog(changeLogFile, hubProjectId, hubProjectName, resultsBuilder, false);
}

public void doRegisterChangelog(String changelogFilepath, UUID hubProjectId, String hubProjectName, CommandResultsBuilder resultsBuilder, boolean skipPromptIfOneProject) throws LiquibaseException, CommandLineParsingException {
try (PrintWriter output = new PrintWriter(resultsBuilder.getOutputStream())) {

HubChangeLog hubChangeLog;
final HubService service = Scope.getCurrentScope().getSingleton(HubServiceFactory.class).getService();
//
// CHeck for existing changeLog file
// Check for existing changeLog file using the untouched changelog filepath
//
DatabaseChangeLog databaseChangeLog = parseChangeLogFile(changeLogFile);
DatabaseChangeLog databaseChangeLog = parseChangeLogFile(changelogFilepath);
if (databaseChangeLog == null) {
throw new CommandExecutionException("Cannot parse "+changeLogFile);
throw new CommandExecutionException("Cannot parse "+changelogFilepath);
}

final String changeLogId = databaseChangeLog.getChangeLogId();
if (changeLogId != null) {
hubChangeLog = service.getHubChangeLog(UUID.fromString(changeLogId));
if (hubChangeLog != null) {
throw new CommandExecutionException("Changelog '" + changeLogFile +
throw new CommandExecutionException("Changelog '" + changelogFilepath +
"' is already registered with changeLogId '" + changeLogId + "' to project '" +
hubChangeLog.getProject().getName() + "' with project ID '" + hubChangeLog.getProject().getId().toString() + "'.\n" +
"For more information visit https://docs.liquibase.com.");
"For more information visit https://docs.liquibase.com.", new ChangeLogAlreadyRegisteredException(hubChangeLog));
} else {
throw new CommandExecutionException("Changelog '" + changeLogFile +
throw new CommandExecutionException("Changelog '" + changelogFilepath +
"' is already registered with changeLogId '" + changeLogId + "'.\n" +
"For more information visit https://docs.liquibase.com.");
"For more information visit https://docs.liquibase.com.", new ChangeLogAlreadyRegisteredException());
}
}

Expand All @@ -118,19 +123,20 @@ public void run(CommandResultsBuilder resultsBuilder) throws Exception {
}
output.print("\nProject '" + project.getName() + "' created with project ID '" + project.getId() + "'.\n\n");
} else {
project = retrieveOrCreateProject(service, commandScope);
project = retrieveOrCreateProject(service, changelogFilepath, skipPromptIfOneProject);
if (project == null) {
throw new CommandExecutionException("Your changelog " + changeLogFile + " was not registered to any Liquibase Hub project. You can still run Liquibase commands, but no data will be saved in your Liquibase Hub account for monitoring or reports. Learn more at https://hub.liquibase.com.");
throw new CommandExecutionException("Your changelog " + changelogFilepath + " was not registered to any Liquibase Hub project. You can still run Liquibase commands, but no data will be saved in your Liquibase Hub account for monitoring or reports. Learn more at https://hub.liquibase.com.");
}
}

//
// Go create the Hub Changelog
//
String changelogFilename = Paths.get(databaseChangeLog.getFilePath()).getFileName().toString();
HubChangeLog newChangeLog = new HubChangeLog();
newChangeLog.setProject(project);
newChangeLog.setFileName(databaseChangeLog.getFilePath());
newChangeLog.setName(databaseChangeLog.getFilePath());
newChangeLog.setFileName(changelogFilename);
newChangeLog.setName(changelogFilename);

hubChangeLog = service.createChangeLog(newChangeLog);

Expand All @@ -139,28 +145,30 @@ public void run(CommandResultsBuilder resultsBuilder) throws Exception {
// Add the registered changelog ID to the results so that
// the caller can use it
//
ChangelogRewriter.ChangeLogRewriterResult changeLogRewriterResult =
ChangelogRewriter.addChangeLogId(changeLogFile, hubChangeLog.getId().toString(), databaseChangeLog);
ChangelogRewriter.ChangeLogRewriterResult changeLogRewriterResult = ChangelogRewriter.addChangeLogId(changelogFilepath, hubChangeLog.getId().toString(), databaseChangeLog);

if (changeLogRewriterResult.success) {
Scope.getCurrentScope().getLog(RegisterChangelogCommandStep.class).info(changeLogRewriterResult.message);
output.println("* Changelog file '" + changeLogFile + "' with changelog ID '" + hubChangeLog.getId().toString() + "' has been " +
output.println("* Changelog file '" + changelogFilepath + "' with changelog ID '" + hubChangeLog.getId().toString() + "' has been " +
"registered to Project "+project.getName() );
resultsBuilder.addResult("statusCode", 0);
resultsBuilder.addResult(REGISTERED_CHANGELOG_ID.getName(), hubChangeLog.getId().toString());
}
}
}

private Project retrieveOrCreateProject(HubService service, CommandScope commandScope) throws CommandLineParsingException, LiquibaseException, LiquibaseHubException {
private Project retrieveOrCreateProject(HubService service, String changeLogFile, boolean skipPromptIfOneProject) throws CommandLineParsingException, LiquibaseException, LiquibaseHubException {
final UIService ui = Scope.getCurrentScope().getUI();
String changeLogFile = commandScope.getArgumentValue(CHANGELOG_FILE_ARG);

Project project = null;
List<Project> projects = getProjectsFromHub();
if (skipPromptIfOneProject && projects.size() == 1) {
return projects.get(0);
}
boolean done = false;
String input = null;
while (!done) {
input = readProjectFromConsole(projects, commandScope);
input = readProjectFromConsole(projects, changeLogFile);
try {
if (input.equalsIgnoreCase("C")) {
String projectName = readProjectNameFromConsole();
Expand Down Expand Up @@ -223,11 +231,11 @@ private String readProjectNameFromConsole() throws CommandLineParsingException {
return StringUtil.trimToEmpty(input);
}

private String readProjectFromConsole(List<Project> projects, CommandScope commandScope) throws CommandLineParsingException {
private String readProjectFromConsole(List<Project> projects, String changeLogFile) throws CommandLineParsingException {
final UIService ui = Scope.getCurrentScope().getUI();

StringBuilder prompt = new StringBuilder("Registering a changelog connects Liquibase operations to a Project for monitoring and reporting.\n");
prompt.append("Register changelog " + commandScope.getArgumentValue(CHANGELOG_FILE_ARG) + " to an existing Project, or create a new one.\n");
prompt.append("Register changelog " + changeLogFile + " to an existing Project, or create a new one.\n");

prompt.append("Please make a selection:\n");

Expand Down
Expand Up @@ -40,13 +40,18 @@ public int getPriority() {

@Override
public void open(String url, Driver driverObject, Properties driverProperties) throws DatabaseException {
String driverClassName = driverObject.getClass().getName();
String errorMessage = "Connection could not be created to " + url + " with driver " + driverClassName;
try {
this.con = driverObject.connect(url, driverProperties);
if (this.con == null) {
throw new DatabaseException("Connection could not be created to " + url + " with driver " + driverObject.getClass().getName() + ". Possibly the wrong driver for the given database URL");
throw new DatabaseException(errorMessage + ". Possibly the wrong driver for the given database URL");
}
} catch (SQLException sqle) {
throw new DatabaseException("Connection could not be created to " + url + " with driver " + driverObject.getClass().getName() + ". " + sqle.getMessage());
if (driverClassName.equals("org.h2.Driver")) {
errorMessage += ". Make sure your H2 database is active and accessible by opening a new terminal window, run \"liquibase init start-h2\", and then return to this terminal window to run commands";
}
throw new DatabaseException(errorMessage + ". " + sqle.getMessage());
}
}

Expand Down
@@ -0,0 +1,27 @@
package liquibase.exception;

import liquibase.hub.model.HubChangeLog;

/**
* Exception class indicating that a particular changelog has already been registered with Hub.
*/
public class ChangeLogAlreadyRegisteredException extends Exception {

/**
* If present, the changelog metadata from Hub. If null, it can be assumed that the changelog has been registered
* with some organization which the current API key cannot access.
*/
private final HubChangeLog hubChangeLog;

public ChangeLogAlreadyRegisteredException() {
this(null);
}

public ChangeLogAlreadyRegisteredException(HubChangeLog hubChangeLog) {
this.hubChangeLog = hubChangeLog;
}

public HubChangeLog getHubChangeLog() {
return hubChangeLog;
}
}
6 changes: 6 additions & 0 deletions liquibase-core/src/main/java/liquibase/hub/HubService.java
Expand Up @@ -45,6 +45,8 @@ public interface HubService extends Plugin, PrioritizedService {

Operation createOperation(String operationType, String operationCommand, HubChangeLog changeLog, Connection connection) throws LiquibaseHubException;

Operation createOperationInOrganization(String operationType, String operationCommand, UUID organizationId) throws LiquibaseHubException;

OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent) throws LiquibaseException;

/**
Expand All @@ -56,7 +58,11 @@ public interface HubService extends Plugin, PrioritizedService {
*/
String shortenLink(String url) throws LiquibaseException;

OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent, UUID organizationId) throws LiquibaseException;

void sendOperationChangeEvent(OperationChangeEvent operationChangeEvent) throws LiquibaseException;

void sendOperationChanges(OperationChange operationChange) throws LiquibaseHubException;

CoreInitOnboardingResponse validateOnboardingToken(String token) throws LiquibaseHubException;
}
15 changes: 15 additions & 0 deletions liquibase-core/src/main/java/liquibase/hub/HubServiceFactory.java
Expand Up @@ -147,6 +147,11 @@ public Operation createOperation(String operationType, String operationCommand,
return null;
}

@Override
public Operation createOperationInOrganization(String operationType, String operationCommand, UUID organizationId) throws LiquibaseHubException {
return null;
}

@Override
public OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent) throws LiquibaseException {
return null;
Expand All @@ -157,6 +162,11 @@ public String shortenLink(String url) throws LiquibaseException {
return null;
}

@Override
public OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent, UUID organizationId) throws LiquibaseException {
return null;
}

@Override
public void sendOperationChangeEvent(OperationChangeEvent operationChangeEvent) throws LiquibaseException {

Expand All @@ -166,5 +176,10 @@ public void sendOperationChangeEvent(OperationChangeEvent operationChangeEvent)
public void sendOperationChanges(OperationChange operationChange) throws LiquibaseHubException {

}

@Override
public CoreInitOnboardingResponse validateOnboardingToken(String token) throws LiquibaseHubException {
return null;
}
}
}
63 changes: 52 additions & 11 deletions liquibase-core/src/main/java/liquibase/hub/core/MockHubService.java
Expand Up @@ -18,11 +18,15 @@ public class MockHubService implements HubService {
public static UUID failUUID = UUID.randomUUID();
public static UUID notFoundChangeLogUUID = UUID.randomUUID();
public static Date operationCreateDate;
public static String apiKey = UUID.randomUUID().toString();
public static UUID organizationId = UUID.randomUUID();
public static Integer numberOfProjectsInList = null;
public static HubChangeLog lastCreatedChangelog = null;

public List<Project> returnProjects = new ArrayList<>();
public List<Connection> returnConnections;
public List<HubChangeLog> returnChangeLogs = new ArrayList<>();
public SortedMap<String, List> sentObjects = new TreeMap<>();
public static SortedMap<String, List> sentObjects = new TreeMap<>();
public boolean online = true;

@Override
Expand Down Expand Up @@ -59,21 +63,34 @@ public HubChangeLog createChangeLog(HubChangeLog hubChangeLog) throws LiquibaseE
randomUUID = UUID.randomUUID();
}
hubChangeLog.setId(randomUUID);
lastCreatedChangelog = hubChangeLog;
return hubChangeLog;
}

@Override
public List<Project> getProjects() throws LiquibaseHubException {
Project project1 = new Project();
project1.setId(UUID.fromString("72e4bc5a-5404-45be-b9e1-280a80c98cbf"));
project1.setName("Project 1");
project1.setCreateDate(new Date());

Project project2 = new Project();
project2.setId(UUID.randomUUID());
project2.setName("Project 2");
project2.setCreateDate(new Date());
return Arrays.asList(project1, project2);
if (numberOfProjectsInList == null) {
Project project1 = new Project();
project1.setId(UUID.fromString("72e4bc5a-5404-45be-b9e1-280a80c98cbf"));
project1.setName("Project 1");
project1.setCreateDate(new Date());

Project project2 = new Project();
project2.setId(UUID.randomUUID());
project2.setName("Project 2");
project2.setCreateDate(new Date());
return Arrays.asList(project1, project2);
} else {
List<Project> projects = new ArrayList<>(numberOfProjectsInList);
for(int i = 0; i < numberOfProjectsInList; i++) {
Project project = new Project();
project.setId(UUID.randomUUID());
project.setName("Project " + i + 1);
project.setCreateDate(new Date());
projects.add(project);
}
return projects;
}
}

@Override
Expand Down Expand Up @@ -156,6 +173,11 @@ public Operation createOperation(String operationType, String operationCommand,
return null;
}

@Override
public Operation createOperationInOrganization(String operationType, String operationCommand, UUID organizationId) throws LiquibaseHubException {
return null;
}

@Override
public OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent) throws LiquibaseException {
return null;
Expand All @@ -176,6 +198,25 @@ public String shortenLink(String url) throws LiquibaseException {
return null;
}

@Override
public OperationEvent sendOperationEvent(Operation operation, OperationEvent operationEvent, UUID organizationId) throws LiquibaseException {
sentObjects.computeIfAbsent("sendOperationEvent", k -> new ArrayList<>()).add(operationEvent);
return null;
}

@Override
public CoreInitOnboardingResponse validateOnboardingToken(String token) throws LiquibaseHubException {
CoreInitOnboardingResponse response = new CoreInitOnboardingResponse();
ApiKey ak = new ApiKey();
ak.setKey(apiKey);
response.setApiKey(ak);
Organization organization = new Organization();
organization.setId(organizationId);
organization.setName("neworg");
response.setOrganization(organization);
return response;
}

public void reset() {
randomUUID = UUID.randomUUID();

Expand Down

0 comments on commit 0bb2eae

Please sign in to comment.