Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit b4fc24a

Browse files
committed
#351 Roles are now deployed correctly in one phase
Removed DeployRoleWithCommentsTest, as it's no longer valid since roles are already read into an ObjectNode now.
1 parent 1e28c13 commit b4fc24a

File tree

18 files changed

+533
-394
lines changed

18 files changed

+533
-394
lines changed

src/main/java/com/marklogic/appdeployer/command/AbstractCommand.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -480,15 +480,6 @@ protected boolean cmaEndpointExists(CommandContext context) {
480480
return new ConfigurationManager(context.getManageClient()).endpointExists();
481481
}
482482

483-
protected void ignoreIncrementalCheckForFile(File file) {
484-
if (resourceFilenameFilter instanceof IncrementalFilenameFilter) {
485-
((IncrementalFilenameFilter) resourceFilenameFilter).ignoreIncrementalCheckForFile(file);
486-
} else {
487-
logger.warn("resourceFilenameFilter does not implement " + IncrementalFilenameFilter.class.getName() + ", and thus " +
488-
"ignoreIncrementalCheckForFile for file " + file.getAbsolutePath() + " cannot be invoked");
489-
}
490-
}
491-
492483
protected void setIncrementalMode(boolean incrementalMode) {
493484
if (resourceFilenameFilter instanceof IncrementalFilenameFilter) {
494485
((IncrementalFilenameFilter) resourceFilenameFilter).setIncrementalMode(incrementalMode);

src/main/java/com/marklogic/appdeployer/command/IncrementalFilenameFilter.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.marklogic.appdeployer.command;
22

3-
import java.io.File;
43
import java.io.FilenameFilter;
54

65
/**
@@ -9,14 +8,6 @@
98
*/
109
public interface IncrementalFilenameFilter extends FilenameFilter {
1110

12-
/**
13-
* If the given File is processed during a deployment, do not perform an incremental check on it - i.e. essentially
14-
* act as though incremental mode is disabled.
15-
*
16-
* @param resourceFile
17-
*/
18-
void ignoreIncrementalCheckForFile(File resourceFile);
19-
2011
/**
2112
* Toggle whether this file performs any incremental check.
2213
*

src/main/java/com/marklogic/appdeployer/command/ResourceFilenameFilter.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public class ResourceFilenameFilter extends LoggingObject implements Incremental
2020
private Pattern includePattern;
2121
private ResourceFileManager resourceFileManager;
2222

23-
private Set<File> filesToIgnoreIncrementalCheck = new HashSet<>();
2423
private boolean incrementalMode = false;
2524

2625
private Set<String> supportedFilenameExtensions = new HashSet<>();
@@ -117,15 +116,7 @@ protected boolean filenameHasSupportedExtension(String filename) {
117116
* @return
118117
*/
119118
protected boolean acceptFileBasedOnIncrementalCheck(File dir, String filename) {
120-
File resourceFile = new File(dir, filename);
121-
if (filesToIgnoreIncrementalCheck.contains(resourceFile)) {
122-
if (logger.isDebugEnabled()) {
123-
logger.debug("Ignoring incremental check for file: " + resourceFile.getAbsolutePath());
124-
}
125-
return true;
126-
} else {
127-
return resourceFileManager.shouldResourceFileBeProcessed(resourceFile);
128-
}
119+
return resourceFileManager.shouldResourceFileBeProcessed(new File(dir, filename));
129120
}
130121

131122
public void setFilenamesToIgnore(Set<String> ignoreFilenames) {
@@ -157,11 +148,6 @@ public void setIncrementalMode(boolean incrementalMode) {
157148
this.incrementalMode = incrementalMode;
158149
}
159150

160-
@Override
161-
public void ignoreIncrementalCheckForFile(File resourceFile) {
162-
this.filesToIgnoreIncrementalCheck.add(resourceFile);
163-
}
164-
165151
public void setResourceFileManager(ResourceFileManager resourceFileManager) {
166152
this.resourceFileManager = resourceFileManager;
167153
}

src/main/java/com/marklogic/appdeployer/command/security/DeployRolesCommand.java

Lines changed: 101 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,38 @@
22

33
import com.fasterxml.jackson.databind.ObjectReader;
44
import com.fasterxml.jackson.databind.node.ObjectNode;
5-
import com.marklogic.appdeployer.command.*;
5+
import com.marklogic.appdeployer.command.AbstractResourceCommand;
6+
import com.marklogic.appdeployer.command.CommandContext;
7+
import com.marklogic.appdeployer.command.SortOrderConstants;
8+
import com.marklogic.appdeployer.command.SupportsCmaCommand;
69
import com.marklogic.mgmt.SaveReceipt;
7-
import com.marklogic.mgmt.api.API;
810
import com.marklogic.mgmt.api.configuration.Configuration;
911
import com.marklogic.mgmt.api.configuration.Configurations;
1012
import com.marklogic.mgmt.api.security.Role;
11-
import com.marklogic.mgmt.mapper.DefaultResourceMapper;
12-
import com.marklogic.mgmt.mapper.ResourceMapper;
13+
import com.marklogic.mgmt.api.security.RoleObjectNodesSorter;
1314
import com.marklogic.mgmt.resource.ResourceManager;
1415
import com.marklogic.mgmt.resource.security.RoleManager;
1516
import com.marklogic.mgmt.util.ObjectMapperFactory;
17+
import com.marklogic.mgmt.util.ObjectNodesSorter;
1618
import com.marklogic.rest.util.ResourcesFragment;
1719

1820
import java.io.File;
1921
import java.io.IOException;
2022
import java.util.ArrayList;
21-
import java.util.HashSet;
2223
import java.util.List;
23-
import java.util.Set;
2424

25+
/**
26+
* As of 3.15.0, this no longer deploys roles in two phases. This is due to the new sorting class, which uses a
27+
* topological sort to properly account for role dependencies.
28+
* <p>
29+
* However, to take advantage of this sorting, this class instructs the parent class to always construct a CMA request
30+
* for all of the roles that are found. This allows for all of the roles to be sorted easily, as they're in a list of
31+
* ObjectNode objects. Once the CMA request is ready to be submitted, this class then checks to see if CMA should
32+
* actually be used. If not, then each role is submitted individually in the correct order.
33+
*/
2534
public class DeployRolesCommand extends AbstractResourceCommand implements SupportsCmaCommand {
2635

27-
// Used internally
28-
private boolean removeRolesAndPermissionsDuringDeployment = false;
29-
private ResourceMapper resourceMapper;
30-
private Set<String> roleNamesThatDontNeedToBeRedeployed;
31-
32-
private boolean deployRolesInTwoPhases = true;
36+
private ObjectNodesSorter objectNodesSorter = new RoleObjectNodesSorter();
3337

3438
public DeployRolesCommand() {
3539
setExecuteSortOrder(SortOrderConstants.DEPLOY_ROLES);
@@ -40,9 +44,21 @@ public DeployRolesCommand() {
4044
setResourceClassType(Role.class);
4145
}
4246

47+
/**
48+
* This tells the parent class to always build a Configuration, even if CMA isn't available. When it's time to
49+
* deploy the configuration, we'll check to see if CMA truly is available.
50+
*
51+
* @param context
52+
* @return
53+
*/
54+
@Override
55+
protected boolean useCmaForDeployingResources(CommandContext context) {
56+
return true;
57+
}
58+
4359
@Override
4460
public boolean cmaShouldBeUsed(CommandContext context) {
45-
return context.getAppConfig().getCmaConfig().isDeployRoles();
61+
return true;
4662
}
4763

4864
@Override
@@ -51,162 +67,107 @@ public void addResourceToConfiguration(ObjectNode resource, Configuration config
5167
}
5268

5369
/**
54-
* The set of roles is processed twice. The first time, the roles are saved without any default permissions or references to other roles.
55-
* This is to avoid issues where the roles refer to each other or to themselves (via default permissions). The second time, the roles are
56-
* saved with permissions and references to other roles, which is guaranteed to work now that the roles have all been created.
70+
* Before a role configuration can be submitted, the roles within the configuration must be sorted based on their
71+
* dependencies.
5772
* <p>
58-
* For 3.11.0, as part of the new preview feature, the boolean deployRolesInTwoPhases has been added so that the
59-
* process of deploying roles in two phases can be disabled during a preview.
73+
* Then, if CMA is available and the user wants it to be used, the configuration is either submitted or added to a
74+
* combined CMA request. Otherwise, each role will be created individually. In both cases, a check is made on
75+
* each role to see if it does not exist yet and refers to itself. If so, such roles will be created first without
76+
* any dependencies.
6077
*
6178
* @param context
79+
* @param config
6280
*/
6381
@Override
64-
public void execute(CommandContext context) {
65-
roleNamesThatDontNeedToBeRedeployed = new HashSet<>();
66-
if (deployRolesInTwoPhases && !cmaShouldBeUsed(context)) {
67-
removeRolesAndPermissionsDuringDeployment = true;
68-
if (logger.isInfoEnabled()) {
69-
logger.info("Deploying roles minus their default permissions and references to roles");
70-
}
71-
super.execute(context);
72-
if (logger.isInfoEnabled()) {
73-
logger.info("Redeploying roles that have default permissions and/or references to roles");
74-
}
75-
removeRolesAndPermissionsDuringDeployment = false;
82+
protected void deployConfiguration(CommandContext context, Configuration config) {
83+
List<ObjectNode> roleNodes = config.getRoles();
84+
if (roleNodes == null || roleNodes.isEmpty()) {
85+
return;
7686
}
7787

78-
super.execute(context);
79-
}
80-
81-
@Override
82-
protected void deployConfiguration(CommandContext context, Configuration config) {
83-
submitConfigurationWithRolesThatReferenceThemselves(context, config);
88+
if (objectNodesSorter != null) {
89+
logger.info("Sorting roles before they are saved");
90+
roleNodes = objectNodesSorter.sortObjectNodes(roleNodes);
91+
config.setRoles(roleNodes);
92+
}
8493

85-
if (config.getRoles() != null) {
86-
logger.info("Sorting roles before they are submitted in a CMA request");
87-
config.setRoles(Role.sortObjectNodes(config.getRoles()));
94+
if (context.getAppConfig().getCmaConfig().isDeployRoles() && cmaEndpointExists(context)) {
95+
submitRolesConfigurationViaCma(context, config);
96+
} else {
97+
submitRolesIndividually(context, roleNodes);
8898
}
99+
}
89100

101+
protected void submitRolesConfigurationViaCma(CommandContext context, Configuration config) {
102+
submitConfigurationWithRolesThatReferenceThemselves(context, config.getRoles());
90103
if (context.getAppConfig().getCmaConfig().isCombineRequests()) {
91104
logger.info("Adding roles to combined CMA request");
92105
context.addCmaConfigurationToCombinedRequest(config);
93106
} else {
94-
final String key = getClass().getSimpleName() + "-roles";
95-
if (removeRolesAndPermissionsDuringDeployment) {
96-
context.getContextMap().put(key, new Configurations(config));
97-
} else {
98-
Configurations configs = (Configurations) context.getContextMap().get(key);
99-
if (configs == null) {
100-
// This shouldn't ever happen, but just in case
101-
configs = new Configurations();
102-
}
103-
configs.getConfigs().add(config);
104-
configs.submit(context.getManageClient());
105-
}
107+
super.deployConfiguration(context, config);
106108
}
107109
}
108110

111+
protected void submitRolesIndividually(CommandContext context, List<ObjectNode> roleNodes) {
112+
RoleManager roleManager = new RoleManager(context.getManageClient());
113+
114+
findRolesThatReferenceThemselves(context, roleNodes).forEach(role -> {
115+
roleManager.save(format("{\"role-name\":\"%s\"}", role.getRoleName()));
116+
});
117+
118+
roleNodes.forEach(roleNode -> {
119+
SaveReceipt receipt = saveResource(roleManager, context, roleNode.toString());
120+
afterResourceSaved(roleManager, context, null, receipt);
121+
});
122+
}
123+
109124
/**
110125
* If a role refers to itself via permissions, that role won't be created by CMA. Instead, a separate CMA request
111126
* is constructed, with each such role only having a role-name, and then immediately submitted so that the roles
112127
* are guaranteed to exist. Note that only roles that don't exist yet will be included in this request (if they
113128
* already exist, then no problem will occur).
114129
*
115130
* @param context
116-
* @param config
131+
* @param roles
117132
*/
118-
protected void submitConfigurationWithRolesThatReferenceThemselves(CommandContext context, Configuration config) {
119-
List<ObjectNode> roles = config.getRoles();
120-
if (roles != null && !roles.isEmpty()) {
121-
ObjectReader reader = ObjectMapperFactory.getObjectMapper().readerFor(Role.class);
122-
List<ObjectNode> rolesThatReferenceThemselves = new ArrayList<>();
123-
ResourcesFragment rolesXml = new RoleManager(context.getManageClient()).getAsXml();
124-
125-
roles.forEach(role -> {
126-
try {
127-
Role r = reader.readValue(role);
128-
if (r.hasPermissionWithOwnRoleName() && !rolesXml.resourceExists(r.getRoleName())) {
129-
ObjectNode node = ObjectMapperFactory.getObjectMapper().createObjectNode();
130-
node.put("role-name", r.getRoleName());
131-
rolesThatReferenceThemselves.add(node);
132-
}
133-
} catch (IOException e) {
134-
throw new RuntimeException("Unable to read ObjectNode into Role; node: " + role, e);
135-
}
133+
protected void submitConfigurationWithRolesThatReferenceThemselves(CommandContext context, List<ObjectNode> roles) {
134+
List<Role> rolesThatReferenceThemselves = findRolesThatReferenceThemselves(context, roles);
135+
136+
if (!rolesThatReferenceThemselves.isEmpty()) {
137+
Configuration roleNamesOnlyConfig = new Configuration();
138+
rolesThatReferenceThemselves.forEach(role -> {
139+
ObjectNode node = ObjectMapperFactory.getObjectMapper().createObjectNode();
140+
node.put("role-name", role.getRoleName());
141+
roleNamesOnlyConfig.addRole(node);
136142
});
137-
138-
if (!rolesThatReferenceThemselves.isEmpty()) {
139-
Configuration roleNamesOnlyConfig = new Configuration();
140-
rolesThatReferenceThemselves.forEach(role -> roleNamesOnlyConfig.addRole(role));
141-
logger.info("Submitting CMA configuration containing roles that reference themselves and do not yet exist");
142-
new Configurations(roleNamesOnlyConfig).submit(context.getManageClient());
143-
}
143+
logger.info("Submitting CMA configuration containing roles that reference themselves and do not yet exist");
144+
new Configurations(roleNamesOnlyConfig).submit(context.getManageClient());
144145
}
145146
}
146147

147148
/**
148-
* If the resource was saved during the first pass - i.e. when roles and permissions have been removed from the role
149-
* - then it must be processed during the second pass so that those roles/permissions can be added back. Thus, the
150-
* incremental check on the file must be ignored. Note that this only matters during an incremental deploy, which
151-
* means that the ResourceReference should have a single File in it.
152-
*/
153-
@Override
154-
protected void afterResourceSaved(ResourceManager mgr, CommandContext context, ResourceReference resourceReference, SaveReceipt receipt) {
155-
if (removeRolesAndPermissionsDuringDeployment && resourceReference != null) {
156-
ignoreIncrementalCheckForFile(resourceReference.getLastFile());
157-
}
158-
super.afterResourceSaved(mgr, context, resourceReference, receipt);
159-
}
160-
161-
/**
162-
* If this is the first time roles are being deployed by this command - indicated by the removeRolesAndPermissionsDuringDeployment
163-
* class variable - then each payload is modified so that default permissions and role references are not included,
164-
* thus ensuring that the role can be created successfully.
165-
* <p>
166-
* If this is the second time that roles are being deployed by this command, then the entire payload is sent. However,
167-
* if the role doesn't have any default permissions or role references, it will not be deployed a second time, as
168-
* there was nothing missing from the first deployment of the role.
149+
* Returns a list of roles, one for each role in the given ObjectNode list that does not exist yet and refers to
150+
* itself.
169151
*
170-
* @param mgr
171152
* @param context
172-
* @param f
173-
* @param payload
153+
* @param roles
174154
* @return
175155
*/
176-
@Override
177-
protected String adjustPayloadBeforeSavingResource(CommandContext context, File f, String payload) {
178-
payload = super.adjustPayloadBeforeSavingResource(context, f, payload);
179-
180-
if (resourceMapper == null) {
181-
API api = new API(context.getManageClient(), context.getAdminManager());
182-
resourceMapper = new DefaultResourceMapper(api);
183-
}
184-
185-
Role role = resourceMapper.readResource(payload, Role.class);
186-
187-
// Is this the first time the roles are being deployed?
188-
if (removeRolesAndPermissionsDuringDeployment) {
189-
if (role.hasPermissionsOrRoles()) {
190-
role.clearPermissionsAndRoles();
191-
return role.getJson();
192-
} else {
193-
roleNamesThatDontNeedToBeRedeployed.add(role.getRoleName());
194-
return payload;
195-
}
196-
}
197-
// Else it's the second time roles are being deployed, but no need to deploy a role if it doesn't have any default permissions or role references
198-
else if (roleNamesThatDontNeedToBeRedeployed.contains(role.getRoleName())) {
199-
if (logger.isInfoEnabled()) {
200-
logger.info("Not redeploying role " + role.getRoleName() + ", as it does not have any default permissions or references to other roles");
156+
protected List<Role> findRolesThatReferenceThemselves(CommandContext context, List<ObjectNode> roles) {
157+
ObjectReader reader = ObjectMapperFactory.getObjectMapper().readerFor(Role.class);
158+
List<Role> rolesThatReferenceThemselves = new ArrayList<>();
159+
ResourcesFragment rolesXml = new RoleManager(context.getManageClient()).getAsXml();
160+
roles.forEach(role -> {
161+
try {
162+
Role r = reader.readValue(role);
163+
if (r.hasPermissionWithOwnRoleName() && !rolesXml.resourceExists(r.getRoleName())) {
164+
rolesThatReferenceThemselves.add(r);
165+
}
166+
} catch (IOException e) {
167+
throw new RuntimeException("Unable to read ObjectNode into Role; node: " + role, e);
201168
}
202-
return null;
203-
}
204-
// Else log a message to indicate that the role is being redeployed
205-
else if (logger.isInfoEnabled()) {
206-
logger.info("Redeploying role " + role.getRoleName() + " with default permissions and references to other roles included");
207-
}
208-
209-
return payload;
169+
});
170+
return rolesThatReferenceThemselves;
210171
}
211172

212173
protected File[] getResourceDirs(CommandContext context) {
@@ -218,8 +179,13 @@ protected ResourceManager getResourceManager(CommandContext context) {
218179
return new RoleManager(context.getManageClient());
219180
}
220181

182+
@Deprecated
221183
public void setDeployRolesInTwoPhases(boolean deployRolesInTwoPhases) {
222-
this.deployRolesInTwoPhases = deployRolesInTwoPhases;
184+
// No longer needed as of 3.15.0
185+
}
186+
187+
public void setObjectNodesSorter(ObjectNodesSorter objectNodesSorter) {
188+
this.objectNodesSorter = objectNodesSorter;
223189
}
224190
}
225191

0 commit comments

Comments
 (0)