diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..9427c75e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = tab +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.java] +indent_size = 4 diff --git a/src/main/java/com/marklogic/appdeployer/command/AbstractCommand.java b/src/main/java/com/marklogic/appdeployer/command/AbstractCommand.java index 07d818c4..b710992a 100644 --- a/src/main/java/com/marklogic/appdeployer/command/AbstractCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/AbstractCommand.java @@ -37,7 +37,7 @@ public Integer getExecuteSortOrder() { /** * Convenience method for setting the names of files to ignore when reading resources from a directory. Will * preserve any filenames already being ignored on the underlying FilenameFilter. - * + * * @param filenames */ public void setFilenamesToIgnore(String... filenames) { @@ -59,7 +59,7 @@ public void setFilenamesToIgnore(String... filenames) { /** * Simplifies reading the contents of a File into a String. - * + * * @param f * @return */ @@ -73,17 +73,29 @@ protected String copyFileToString(File f) { } } + /** + * Convenience function for reading the file into a string and replace tokens as well. Assumes this is not + * for a test-only resource. + * + * @param f + * @param context + * @return + */ + protected String copyFileToString(File f, CommandContext context) { + String str = copyFileToString(f); + return str != null ? tokenReplacer.replaceTokens(str, context.getAppConfig(), false) : str; + } + /** * Provides a basic implementation for saving a resource defined in a File, including replacing tokens. - * + * * @param mgr * @param context * @param f * @return */ protected SaveReceipt saveResource(ResourceManager mgr, CommandContext context, File f) { - String payload = copyFileToString(f); - payload = tokenReplacer.replaceTokens(payload, context.getAppConfig(), false); + String payload = copyFileToString(f, context); SaveReceipt receipt = mgr.save(payload); if (storeResourceIdsAsCustomTokens) { storeTokenForResourceId(receipt, context); @@ -95,7 +107,7 @@ protected SaveReceipt saveResource(ResourceManager mgr, CommandContext context, * Any resource that may be referenced by its ID by another resource will most likely need its ID stored as a custom * token so that it can be referenced by the other resource. To enable this, the subclass should set * storeResourceIdAsCustomToken to true. - * + * * @param receipt * @param context */ diff --git a/src/main/java/com/marklogic/appdeployer/command/AbstractResourceCommand.java b/src/main/java/com/marklogic/appdeployer/command/AbstractResourceCommand.java index 059d8f74..6f99020d 100644 --- a/src/main/java/com/marklogic/appdeployer/command/AbstractResourceCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/AbstractResourceCommand.java @@ -41,7 +41,7 @@ public void execute(CommandContext context) { /** * Subclasses can override this to add functionality after a resource has been saved. - * + * * @param mgr * @param context * @param resourceFile @@ -77,13 +77,13 @@ public void undo(CommandContext context) { * delete the resource. This has been necessary when deleting two app servers in a row - for some reason, the 2nd * delete will intermittently fail with a connection reset error, but the app server is in fact deleted * successfully. - * + * * @param mgr * @param context * @param f */ protected void deleteResource(final ResourceManager mgr, CommandContext context, File f) { - final String payload = tokenReplacer.replaceTokens(copyFileToString(f), context.getAppConfig(), false); + final String payload = copyFileToString(f, context); try { if (restartAfterDelete) { context.getAdminManager().invokeActionRequiringRestart(new ActionRequiringRestart() { diff --git a/src/main/java/com/marklogic/appdeployer/command/alert/DeployAlertRulesCommand.java b/src/main/java/com/marklogic/appdeployer/command/alert/DeployAlertRulesCommand.java index 6a3d9821..8d425b9e 100644 --- a/src/main/java/com/marklogic/appdeployer/command/alert/DeployAlertRulesCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/alert/DeployAlertRulesCommand.java @@ -44,7 +44,7 @@ protected void deployRulesInDirectory(File dir, CommandContext context) { * parse its contents. */ for (File f : listFilesInDirectory(dir)) { - String payload = copyFileToString(f); + String payload = copyFileToString(f, context); String actionName = payloadParser.getPayloadFieldValue(payload, "action-name"); AlertRuleManager mgr = new AlertRuleManager(context.getManageClient(), dbName, configUri, actionName); saveResource(mgr, context, f); diff --git a/src/main/java/com/marklogic/appdeployer/command/cpf/AbstractCpfResourceCommand.java b/src/main/java/com/marklogic/appdeployer/command/cpf/AbstractCpfResourceCommand.java index 988fdfb4..ebb9545f 100644 --- a/src/main/java/com/marklogic/appdeployer/command/cpf/AbstractCpfResourceCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/cpf/AbstractCpfResourceCommand.java @@ -20,8 +20,7 @@ public void execute(CommandContext context) { if (dir.exists()) { AbstractCpfResourceManager mgr = getResourceManager(context); for (File f : listFilesInDirectory(dir)) { - String payload = copyFileToString(f); - payload = tokenReplacer.replaceTokens(payload, config, false); + String payload = copyFileToString(f, context); mgr.save(config.getTriggersDatabaseName(), payload); } } diff --git a/src/main/java/com/marklogic/appdeployer/command/forests/ConfigureForestReplicasCommand.java b/src/main/java/com/marklogic/appdeployer/command/forests/ConfigureForestReplicasCommand.java index 0a2937e2..742829ea 100644 --- a/src/main/java/com/marklogic/appdeployer/command/forests/ConfigureForestReplicasCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/forests/ConfigureForestReplicasCommand.java @@ -16,7 +16,7 @@ * Command for configuring - i.e. creating and setting - replica forests for existing databases and/or primary forests. * It's normally easier to just specify the databases that you want to configure forest replicas for, but this command * does provide the ability to configure replicas for specific forests. - * + * * Very useful for the out-of-the-box forests such as Security, Schemas, App-Services, and Meters, which normally need * replicas for failover in a cluster. */ @@ -38,7 +38,7 @@ public ConfigureForestReplicasCommand() { /** * Allows for the map of database names and counts to be configured as a comma-delimited string of the form: * "dbName,replicaCount,dbName,replicaCount,etc". - * + * * @param str */ public void setDatabaseNamesAndReplicaCountsAsString(String str) { @@ -96,7 +96,7 @@ public void undo(CommandContext context) { if (str != null) { setDatabaseNamesAndReplicaCountsAsString(str); } - + DatabaseManager dbMgr = new DatabaseManager(context.getManageClient()); ForestManager forestMgr = new ForestManager(context.getManageClient()); @@ -132,7 +132,7 @@ protected void deleteReplicas(String forestName, ForestManager forestMgr) { * For the given database, find all of its primary forests. Then for each primary forest, just call * configureReplicaForests? And that should be smart enough to say - if the primary forest already has replicas, * then don't do anything. - * + * * @param databaseName * @param replicaCount * @param hostIds @@ -151,7 +151,7 @@ protected void configureDatabaseReplicaForests(String databaseName, int replicaC /** * Creates forests as needed (they may already exists) and then sets those forests as the replicas for the given * primaryForestName. - * + * * @param forestIdOrName * @param replicaCount * @param hostIds @@ -178,7 +178,7 @@ protected void configureReplicaForests(String forestIdOrName, int replicaCount, if (!hostId.equals(primaryForestHostId)) { for (int i = 0; i < replicaCount; i++) { String name = forestIdOrName + "-" + resourceCounter; - forestMgr.createForestWithName(name, hostId); + forestMgr.createJsonForestWithName(name, hostId); replicaNamesAndHostIds.put(name, hostId); resourceCounter++; } diff --git a/src/main/java/com/marklogic/appdeployer/command/forests/DeployCustomForestsCommand.java b/src/main/java/com/marklogic/appdeployer/command/forests/DeployCustomForestsCommand.java new file mode 100644 index 00000000..cb0f4281 --- /dev/null +++ b/src/main/java/com/marklogic/appdeployer/command/forests/DeployCustomForestsCommand.java @@ -0,0 +1,39 @@ +package com.marklogic.appdeployer.command.forests; + +import com.marklogic.appdeployer.command.AbstractCommand; +import com.marklogic.appdeployer.command.CommandContext; +import com.marklogic.appdeployer.command.SortOrderConstants; +import com.marklogic.mgmt.forests.ForestManager; + +import java.io.File; + +/** + * Use this command when you want precise control over the forests that are created for a database. It processes + * each directory under ml-config/forests (the name of the directory does not matter, but it makes sense to name + * it after the database that the forests belong to), and each file in a directory can have a single forest object + * or an array of forest objects. + */ +public class DeployCustomForestsCommand extends AbstractCommand { + + public DeployCustomForestsCommand() { + setExecuteSortOrder(SortOrderConstants.DEPLOY_FORESTS); + } + + @Override + public void execute(CommandContext context) { + File dir = new File(context.getAppConfig().getConfigDir().getBaseDir(), "forests"); + for (File f : dir.listFiles()) { + if (f.isDirectory()) { + processDirectory(f, context); + } + } + } + + protected void processDirectory(File dir, CommandContext context) { + ForestManager mgr = new ForestManager(context.getManageClient()); + for (File f : listFilesInDirectory(dir)) { + String payload = copyFileToString(f, context); + mgr.saveJsonForests(payload); + } + } +} diff --git a/src/main/java/com/marklogic/appdeployer/command/forests/DeployForestsCommand.java b/src/main/java/com/marklogic/appdeployer/command/forests/DeployForestsCommand.java index 0c706b39..543c264c 100644 --- a/src/main/java/com/marklogic/appdeployer/command/forests/DeployForestsCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/forests/DeployForestsCommand.java @@ -15,6 +15,10 @@ import com.marklogic.mgmt.hosts.HostManager; /** + * This command is for a simple use case where all the forests created for a database have the same structure, + * but possibly exist on different forests. For more precise control over how forests are created, please see + * DeployCustomForestsCommand. + * * Doesn't yet support deleting forests - currently assumed that this will be done by deleting a database. */ public class DeployForestsCommand extends AbstractCommand { @@ -148,4 +152,4 @@ public boolean isCreateForestsOnEachHost() { public void setCreateForestsOnEachHost(boolean createForestsOnEachHost) { this.createForestsOnEachHost = createForestsOnEachHost; } -} \ No newline at end of file +} diff --git a/src/main/java/com/marklogic/appdeployer/command/security/DeployCertificateAuthoritiesCommand.java b/src/main/java/com/marklogic/appdeployer/command/security/DeployCertificateAuthoritiesCommand.java index e8029dcc..4e270a3c 100644 --- a/src/main/java/com/marklogic/appdeployer/command/security/DeployCertificateAuthoritiesCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/security/DeployCertificateAuthoritiesCommand.java @@ -25,7 +25,7 @@ public void execute(CommandContext context) { if (logger.isInfoEnabled()) { logger.info("Creating certificate authority from file: " + f.getAbsolutePath()); } - String payload = copyFileToString(f); + String payload = copyFileToString(f, context); ResponseEntity response = mgr.create(payload); if (logger.isInfoEnabled()) { logger.info("Created certificate authority, location: " + response.getHeaders().getLocation()); diff --git a/src/main/java/com/marklogic/mgmt/forests/ForestManager.java b/src/main/java/com/marklogic/mgmt/forests/ForestManager.java index bd8abb13..16bf38ba 100644 --- a/src/main/java/com/marklogic/mgmt/forests/ForestManager.java +++ b/src/main/java/com/marklogic/mgmt/forests/ForestManager.java @@ -1,9 +1,11 @@ package com.marklogic.mgmt.forests; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.databind.JsonNode; import com.marklogic.mgmt.AbstractResourceManager; import com.marklogic.mgmt.ManageClient; import com.marklogic.rest.util.Fragment; @@ -20,15 +22,14 @@ public class ForestManager extends AbstractResourceManager { public ForestManager(ManageClient client) { super(client); - setUpdateAllowed(false); } - public void createForestWithName(String name, String host) { + public void createJsonForestWithName(String name, String host) { if (forestExists(name)) { logger.info(format("Forest already exists with name, so not creating: %s", name)); } else { logger.info(format("Creating forest %s on host %s", name, host)); - createForest(format("{\"forest-name\":\"%s\", \"host\":\"%s\"}", name, host)); + createJsonForest(format("{\"forest-name\":\"%s\", \"host\":\"%s\"}", name, host)); logger.info(format("Created forest %s on host %s", name, host)); } } @@ -43,7 +44,24 @@ public void delete(String nameOrId, String level) { } } - public void createForest(String json) { + /** + * Supports either an array of JSON objects or a single JSON object. + * + * @param json + */ + public void saveJsonForests(String json) { + JsonNode node = super.payloadParser.parseJson(json); + if (node.isArray()) { + Iterator iter = node.iterator(); + while (iter.hasNext()) { + save(iter.next().toString()); + } + } else { + save(json); + } + } + + public void createJsonForest(String json) { getManageClient().postJson("/manage/v2/forests", json); } @@ -103,7 +121,7 @@ public void setReplicas(String forestIdOrName, Map replicaNamesA /** * Convenience method for detaching a forest from any replicas it has; this is often used before deleting those * replicas - * + * * @param forestIdOrName */ public void setReplicasToNone(String forestIdOrName) { @@ -112,7 +130,7 @@ public void setReplicasToNone(String forestIdOrName) { /** * Returns a list of IDs for each replica forest for the given forest ID or name. - * + * * @param forestIdOrName * @return */ @@ -124,7 +142,7 @@ public List getReplicaIds(String forestIdOrName) { /** * Deletes (with a level of "full") all replicas for the given forest. - * + * * @param forestIdOrName */ public void deleteReplicas(String forestIdOrName) { diff --git a/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java b/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java index d132a306..3ccd7393 100644 --- a/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java +++ b/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java @@ -28,11 +28,6 @@ public abstract class AbstractAppDeployerTest extends AbstractMgmtTest { protected final static Integer SAMPLE_APP_REST_PORT = 8540; protected final static Integer SAMPLE_APP_TEST_REST_PORT = 8541; - @Autowired - private ManageConfig manageConfig; - - private ConfigurableApplicationContext appManagerContext; - // Intended to be used by subclasses protected AppDeployer appDeployer; protected AppConfig appConfig; @@ -60,24 +55,17 @@ protected void initializeAppDeployer() { /** * Initialize an AppDeployer with the given set of commands. Avoids having to create a Spring configuration. - * + * * @param commands */ protected void initializeAppDeployer(Command... commands) { appDeployer = new SimpleAppDeployer(manageClient, adminManager, commands); } - @After - public void closeAppContext() { - if (appManagerContext != null) { - appManagerContext.close(); - } - } - protected void deploySampleApp() { appDeployer.deploy(appConfig); } - + protected void undeploySampleApp() { try { appDeployer.undeploy(appConfig); @@ -102,7 +90,7 @@ protected LoadModulesCommand buildLoadModulesCommand() { command.setModulesLoader(loader); return command; } - + protected void setConfigBaseDir(String path) { appConfig.getConfigDir().setBaseDir(new File("src/test/resources/" + path)); } diff --git a/src/test/java/com/marklogic/appdeployer/command/forests/DeployCustomForestsTest.java b/src/test/java/com/marklogic/appdeployer/command/forests/DeployCustomForestsTest.java new file mode 100644 index 00000000..5269adb7 --- /dev/null +++ b/src/test/java/com/marklogic/appdeployer/command/forests/DeployCustomForestsTest.java @@ -0,0 +1,39 @@ +package com.marklogic.appdeployer.command.forests; + +import com.marklogic.appdeployer.AbstractAppDeployerTest; +import com.marklogic.appdeployer.ConfigDir; +import com.marklogic.appdeployer.command.databases.DeployContentDatabasesCommand; +import com.marklogic.mgmt.forests.ForestManager; +import org.junit.After; +import org.junit.Test; + +import java.io.File; + +/** + * Verifies that directories under ./forests/ are processed correctly. + */ +public class DeployCustomForestsTest extends AbstractAppDeployerTest { + + @After + public void tearDown() { + //undeploySampleApp(); + } + + @Test + public void test() { + // To avoid hardcoding host names that might cause the test to fail, we use a custom token and assume that + // the host of the Management API will work + appConfig.getCustomTokens().put("%%CUSTOM_HOST%%", super.manageConfig.getHost()); + + appConfig.setConfigDir(new ConfigDir(new File("src/test/resources/sample-app/custom-forests"))); + + initializeAppDeployer(new DeployContentDatabasesCommand(1), new DeployCustomForestsCommand()); + deploySampleApp(); + + ForestManager mgr = new ForestManager(manageClient); + assertTrue("One 'simple' forest should have been created by default", mgr.exists("sample-app-content-1")); + assertTrue(mgr.exists("sample-app-content-custom-1")); + assertTrue(mgr.exists("sample-app-content-custom-2")); + assertTrue(mgr.exists("sample-app-content-custom-3")); + } +} diff --git a/src/test/resources/sample-app/custom-forests/databases/content-database.json b/src/test/resources/sample-app/custom-forests/databases/content-database.json new file mode 100644 index 00000000..49cc15ce --- /dev/null +++ b/src/test/resources/sample-app/custom-forests/databases/content-database.json @@ -0,0 +1,3 @@ +{ + "database-name": "%%DATABASE%%" +} \ No newline at end of file diff --git a/src/test/resources/sample-app/custom-forests/forests/sample-app-content/custom-forests.json b/src/test/resources/sample-app/custom-forests/forests/sample-app-content/custom-forests.json new file mode 100644 index 00000000..baa3ffe5 --- /dev/null +++ b/src/test/resources/sample-app/custom-forests/forests/sample-app-content/custom-forests.json @@ -0,0 +1,14 @@ +[ + { + "forest-name": "sample-app-content-custom-1", + "enabled": true, + "host": "%%CUSTOM_HOST%%", + "database": "%%DATABASE%%" + }, + { + "forest-name": "sample-app-content-custom-2", + "enabled": true, + "host": "%%CUSTOM_HOST%%", + "database": "%%DATABASE%%" + } +] diff --git a/src/test/resources/sample-app/custom-forests/forests/sample-app-content/single-custom-forest.json b/src/test/resources/sample-app/custom-forests/forests/sample-app-content/single-custom-forest.json new file mode 100644 index 00000000..c1dc2c4f --- /dev/null +++ b/src/test/resources/sample-app/custom-forests/forests/sample-app-content/single-custom-forest.json @@ -0,0 +1,6 @@ +{ + "forest-name": "sample-app-content-custom-3", + "enabled": true, + "host": "%%CUSTOM_HOST%%", + "database": "%%DATABASE%%" +}