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

add a create/update/changeId/delete posthook (#193) #242

Merged
merged 5 commits into from
Dec 11, 2023
Merged

Conversation

davidcoutadeur
Copy link
Contributor

No description provided.

@davidcoutadeur davidcoutadeur linked an issue Oct 23, 2023 that may be closed by this pull request
@davidcoutadeur
Copy link
Contributor Author

@soisik or @rouazana if you have some time, could you review this PR?

@coudot coudot requested review from rouazana and soisik October 23, 2023 15:32
@coudot coudot added this to the 2.2 milestone Oct 23, 2023
@soisik
Copy link
Contributor

soisik commented Oct 25, 2023

@davidcoutadeur please rebase your PR since we have merged some important refactoring commits since you have sent this.

@davidcoutadeur
Copy link
Contributor Author

@davidcoutadeur please rebase your PR since we have merged some important refactoring commits since you have sent this.

Yes, I am working on this! Coming soon.

@davidcoutadeur
Copy link
Contributor Author

Done

@davidcoutadeur
Copy link
Contributor Author

The test is missing, I am working on it.

@davidcoutadeur
Copy link
Contributor Author

I have just added the test. Everything seems fine.

@rouazana
Copy link
Contributor

I have some little remarks about the code itself, but nothing very important.

Before that I would like to understand the purpose. I understand that you want to be able to launch a process after each modification/creation/deletion.
My questions are:

  • Why are you using json as an argument to pass the modifications? Given how LSC works, and for consistency, I would more have expected to use LDIF, no?
  • Also it seems strange to have a command argument which can be pretty big (think of changing the members of a group for example), so why not using standard input for that instead?

@davidcoutadeur
Copy link
Contributor Author

Hello

I have some little remarks about the code itself, but nothing very important.

Before that I would like to understand the purpose. I understand that you want to be able to launch a process after each modification/creation/deletion. My questions are:

* Why are you using json as an argument to pass the modifications? Given how LSC works, and for consistency, I would more have expected to use LDIF, no?

It is a topic open to discussion indeed.

The main reason is that I'd like to keep the generic side of the LSC object LscModifications. If later we have a connector to another protocol, this would permit to have a generic modification object sent in the script. In this context, JSON seemed a simple way to sent this modification object.

* Also it seems strange to have a command argument which can be pretty big (think of changing the members of a group for example), so why not using standard input for that instead?

Good point indeed. I'll do the modification to use the standard input instead.

@rouazana
Copy link
Contributor

The main reason is that I'd like to keep the generic side of the LSC object LscModifications. If later we have a connector to another protocol, this would permit to have a generic modification object sent in the script. In this context, JSON seemed a simple way to sent this modification object.

LscModifications will always be the object to pass the modifications, whatever the protocol is used in the services.
I think the LDIF format is nearer from LscModifications than JSON, hence my suggestion to use LDIF.

@soisik
Copy link
Contributor

soisik commented Oct 26, 2023

LscModifications will always be the object to pass the modifications, whatever the protocol is used in the services. I think the LDIF format is nearer from LscModifications than json, hence my suggestion to use ldif.

Sorry to step in, but we could have both, adding an option in LSC configuration to choose the input format for hooks ?
It would also be nice a feature to add to executable plugin - which use ldif as input format - to choose between ldif or json as these scripts are often calling web services, and handling data in json is easier than handling ldif for many developers nowadays.

@rouazana
Copy link
Contributor

Yes it could be an interesting option!

Then did you check how binary attributes are encoded in JSON?

@davidcoutadeur
Copy link
Contributor Author

I have pushed a new commit with:

  • new option "outputFormat" for the LSC modifications ("ldif" or "json")
  • LSC is now pushing LSC modifications as input and not argument anymore

@davidcoutadeur
Copy link
Contributor Author

Then did you check how binary attributes are encoded in JSON?

I have made a check with jpegPhoto, which seems to be passed as base64 in LSC modification (and so in JSON)

@davidcoutadeur
Copy link
Contributor Author

Also tested with userCertificate;binary attribute

The data seems encoded in base64 as well:

[ {
  "attributeName" : "userCertificate;binary",
  "values" : [ "MIIDlzCCAn+..." ],
  "operation" : "REPLACE_VALUES"
} ]

Copy link
Contributor

@rouazana rouazana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left several style comments, but this PR is pretty good!

I like the functionality and the implementation is pretty nice and straightforward, good job!

@@ -63,31 +64,56 @@ public class Hooks {
*
* return nothing
*/
public final static void postSyncHook(final String hook, final LscModifications lm) {
public final static void postSyncHook(final String hook, final String outputFormat, final LscModifications lm) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: in general in Java we don't use static methods. You can construct the object in the constructor of the caller.

String jsonModifications = null;

String format = "";
if( outputFormat.equals("json") ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the parsing of the outputFormat should be done previously, here you should be able to manipulate only an Enum

Comment on lines 86 to 92
if( format.equals("json") ) {
modifications = getJsonModifications(lm);
}
else {
modifications = LdifLayout.format(lm);
}
callHook("create", hook, lm.getMainIdentifier(), format, modifications);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these lines can be easily factorized, and in this case easily integrated in the callHook method

* Method calling the hook
*
* return nothing
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure such comments are really useful

@@ -197,6 +197,13 @@ public String getCondition(LscModificationType operation) {
return result;
}

public String getPostHookOutputFormat() {
if (conf.getHooks() == null || conf.getHooks().getOutputFormat() == null) {
return "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said earlier, the result of this method should be an enum.
Also, I would make clearer here which is the default value.


public String getCreatePostHook() {
if (conf.getHooks() == null || conf.getHooks().getCreatePostHook() == null) {
return "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: I usually prefer returning an Optional to indicate there is a value or not.

But here I did not check what are the consequences, it can be a little more complicated to handle if you are not used to.

reloadJndiConnections();

ObjectMapper mapperCreatedEntry = new ObjectMapper();
JsonNode expectedCreatedEntry = mapperCreatedEntry.readTree("[ { \"attributeName\" : \"objectClass\", \"values\" : [ \"inetOrgPerson\", \"person\", \"top\" ], \"operation\" : \"ADD_VALUES\" }, { \"attributeName\" : \"cn\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"ADD_VALUES\" }, { \"attributeName\" : \"sn\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"ADD_VALUES\" } ]");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add a binary attribute to your test? It will allow to check for regression regarding the handling of binary attributes in hooks.

hookReader.close();
hookFile.delete(); // remove hook log
} catch (Exception e) {
fail("Error while reading hook-ldif-" + operation + ".log");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't really need to try/catch in test, the test will fail anyway in case of unexpected exception.

hookReader.close();
hookFile.delete(); // remove hook log
} catch (Exception e) {
fail("Error while reading hook-json-" + operation + ".log");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem

JsonNode hookOperation = mapper.readTree(jp);
assertEquals(hookOperation, expectedEntry);
}
catch (Exception e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem

@davidcoutadeur
Copy link
Contributor Author

davidcoutadeur commented Oct 27, 2023

Hello,

I have taken all remarks in consideration, and made a new commit 68cd544

3 points remaining:

  • about returning an Option attribute for getCreatePostHook: I have made a fix, but I am not sure it's what you expected @rouazana. Could you give a look?
  • about the binary attribute, it's a good idea. For now I am stuck because I can't figure out how to add a custom binary attribute in a LSC task. (see below)
  • about try / catch in the test file, I knew it wasn't necessary, but I preferred this custom output. (it is a more simple and clearer message for the developper)

The naive way to add a custom binary attribute: (does not work because atob unavailability)

        <dataset>
          <name>userCertificate;binary</name>
          <policy>FORCE</policy>
          <forceValues>
            <string>js:atob("MIIDkTCCAnmgAwIBAgIUDhx/9qofTrT+yNFFvihdDn7rjOQwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCRlIxDTALBgNVBAgMBHRlc3QxDTALBgNVBAcMBHRlc3QxDTALBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxDTALBgNVBAMMBHRlc3QwHhcNMjMxMDI3MTQzMDQxWhcNMzMxMDI0MTQzMDQxWjBYMQswCQYDVQQGEwJGUjENMAsGA1UECAwEdGVzdDENMAsGA1UEBwwEdGVzdDENMAsGA1UECgwEdGVzdDENMAsGA1UECwwEdGVzdDENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdWt0QgFnEi7a1hIJQv4ZdOM5y0GGLHQYNrUNSReArvpkYUY5zasFNVzVHCApRuj0t1NMDrn1gNzKkxTIbYGaWRSn+21J0ow+Nxh2TAQW8dkJnWTksCfyGGGItI5q3ST3EUKnepaAzUYYENcSHRyx7UY/3XuzcW0aGhy4PrVTIHBpyLq0Uzv8nH5nbWM+LYt6YbQMmlAz/psTXIC2dfEZhUb4plLGSo7rZxM5geC6Z+os+I8+uw+mGjps1VP7eGq0jCGHNs2rUHMqBNgLvwMH2WlMXo/iNarAb8fUEPdp59FwiTygBlWAn6GoKHJ1HWPpqMxdtjL2Y5+ZMcp70eJqcCAwEAAaNTMFEwHQYDVR0OBBYEFLwffjUBL/Rp4a6MgeCJiFnCZFu8MB8GA1UdIwQYMBaAFLwffjUBL/Rp4a6MgeCJiFnCZFu8MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACjwsg1Z9PyauoKAhkIfyPTEnlEOCo1nN37c2vnH4fyY6fuBdV6GWtk/u9FCuDmYT/4KDRxe33ZUChwSUX0INgamOarWRES3UoPC1GeOvuMf7uustEMLcHAYZVKXSZUrsOjw+VIZ5XrD6GDE64QtvW5Ve3jf43aGgLf27NF0vhF9+gHOZjjBT33S977HUutMUKfRu9PdHAn8Yb1FmSbAvqqK+SAjn6cJC8l5yS5t0BSNQGbKSA8bPzvWI9HXYVvb+ym6GDrsr+Zad3NrqUSZGzS2JFEDVD9aAikldXu6g02fA5A7nufVePmaG7iTyylO/ZU2lTiJ0SHc2DnO0pg2i+0=")</string>
          </forceValues>
        </dataset>

@davidcoutadeur
Copy link
Contributor Author

test with the binary attribute is now done in d9271d4 thanks to @soisik advice

Copy link
Contributor

@rouazana rouazana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry again a bunch of comments, but it starts to look really nice!


if( hook != null && ! hook.equals("") )
if( hook != null && hook.isPresent() )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks better.
The principle of an optional is to not be null, so you can remove the null check.

Also if instead of a String operationType you continue using LscModificationType, the code can be very simplified by:

hook.ifPresent(h -> callHook(lm.getOperation(), h, lm.getMainIdentifier(), outputFormat, lm)

Comment on lines 165 to 166
LOGGER.error("Error while encoding LSC modifications to json");
LOGGER.error("encoding error: ", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LOGGER.error("Error while encoding LSC modifications to json");
LOGGER.error("encoding error: ", e);
LOGGER.error("Error while encoding LSC modifications to json", e);

Comment on lines 144 to 149
LOGGER.error("Error while calling {} posthook {} with format {} for {}",
operationType,
hook,
outputFormat.toString(),
identifier);
LOGGER.error("posthook error: ", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LOGGER.error("Error while calling {} posthook {} with format {} for {}",
operationType,
hook,
outputFormat.toString(),
identifier);
LOGGER.error("posthook error: ", e);
LOGGER.error("Error while calling {} posthook {} with format {} for {}",
operationType,
hook,
outputFormat.toString(),
identifier,
e);

should work also

public String getCreatePostHook() {
return "";
public Optional<String> getCreatePostHook() {
return Optional.ofNullable(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return Optional.ofNullable(null);
return Optional.empty();

public String getDeletePostHook() {
return "";
public Optional<String> getDeletePostHook() {
return Optional.ofNullable(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return Optional.ofNullable(null);
return Optional.empty();

public String getPostHook(LscModificationType operation) {
return "";
public Optional<String> getPostHook(LscModificationType operation) {
return Optional.ofNullable(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return Optional.ofNullable(null);
return Optional.empty();

public String getPostHook(LscModificationType operation) {
String result = "";
public Optional<String> getPostHook(LscModificationType operation) {
Optional<String> result = Optional.ofNullable(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can simplify this method by removing the result method and returning directly into each case

Hooks.postSyncHook(hook, outputFormat, lm);
Optional<String> hook = syncOptions.getDeletePostHook();
OutputFormat outputFormat = syncOptions.getPostHookOutputFormat();
Hooks hookObject = new Hooks();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hookObject (you can name it hooks) should be initialized in the constructor.

Hooks.postSyncHook(hook, outputFormat, lm);
Optional<String> hook = task.getSyncOptions().getPostHook(modificationType);
OutputFormat outputFormat = task.getSyncOptions().getPostHookOutputFormat();
Hooks hookObject = new Hooks();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem

Optional<String> hook = task.getSyncOptions().getPostHook(modificationType);
OutputFormat outputFormat = task.getSyncOptions().getPostHookOutputFormat();
Hooks hookObject = new Hooks();
hookObject.postSyncHook(hook, outputFormat, lm);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also I think you can inline the parameters

@davidcoutadeur
Copy link
Contributor Author

commit 45d8a32 is fixing the last bunch of comments.

@davidcoutadeur davidcoutadeur merged commit 505580e into master Dec 11, 2023
1 check failed
@davidcoutadeur davidcoutadeur deleted the 193-hook branch December 11, 2023 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hook / Trigger system for add/delete/modify/modrdn
4 participants