Develop Your IdentityIQ Task Definition Like a Pro!

Introduction

I often joke that IdentityIQ is so extensible that you could even use it to control your coffee machine. In reality, we must always consider whether extending the standard functionality of IIQ is a good idea. What is clear, however, is that creating a custom Task based on a rule is very handy. As many of you probably already know, you can use a BeanShell rule to perform the necessary tasks. However, there are some tips and tricks that can make your task provide much more information during and after execution and respond better.

Compiled or Beanshell?

My first tip is to use a compiled Java class for developing your Task. Besides the fact that this is (slightly) better for performance, your favorite IDE (I use IntelliJ) immediately gives you feedback if there is an error in your code.

To use your class in a Task, you need to inherit from sailpoint.task.AbstractTaskExecutor. This requires you to override the execute and terminate methods. The execute method provides you with the SailPointContext, allowing you to interact with SailPoint objects, and a taskResult, which can influence what is visible in the UI.

public class MyCustomTask extends AbstractTaskExecutor {
  @Override
  public void execute(SailPointContext context, TaskSchedule taskSchedule, TaskResult taskResult, Attributes<String, Object> args) throws Exception {
    //Your execution code
  }
  
  @Override
  public boolean terminate() {
    //Your termination code
  }
}

Preparing Your TaskDefinition

The starting point for executing your task is the artifact TaskDefinition. There are two very important configurations:

  1. The executor attribute must refer to your created Java class. executor="com.sits.mycustomtask". This tells IIQ to call the execute method of your class when starting it.
  2. During the execution of the Task, you want to provide feedback to the end user. Therefore, it is important that the page refreshes every few seconds to retrieve the new status from the TaskResult object. Set the progressMode attribute to String.
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE TaskDefinition PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<TaskDefinition executor="com.sits.MyCustomTask" name="Test task" progressMode="String" resultAction="Delete" subType="task_item_type_generic" type="Generic">
  <Attributes>
    <Map>
      <entry key="taskCompletionEmailNotify" value="Disabled"/>
      <entry key="taskCompletionEmailRecipients"/>
      <entry key="taskCompletionEmailTemplate"/>
    </Map>
  </Attributes>
  <Description>Your task description</Description>
  <Owner>
    <Reference class="sailpoint.object.Identity" name="spadmin"/>
  </Owner>
</TaskDefinition>

Configuration

When you create a run rule TaskDefinition from the UI, you get a default config attribute, where you can pass your configuration as CSV. For an end user, it is much easier if you have a clear description for each configuration option and also validation on it. This can be done by adding a signature to your TaskDefinition. In your class, you receive these fields via the args parameter in your execute method.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE TaskDefinition PUBLIC "sailpoint.dtd" "sailpoint.dtd">
<TaskDefinition executor="com.sits.MyCustomTask" name="Test task" progressMode="String" resultAction="Delete" subType="task_item_type_generic" type="Generic">
  <Attributes>
    <Map>
      <entry key="filter" value="filter value"/>
      <entry key="taskCompletionEmailNotify" value="Disabled"/>
      <entry key="taskCompletionEmailRecipients"/>
      <entry key="taskCompletionEmailTemplate"/>
      <entry key="threshold" value="50"/>
    </Map>
  </Attributes>
  <Description>Your task description</Description>
  <Owner>
    <Reference class="sailpoint.object.Identity" name="spadmin"/>
  </Owner>
  <Signature>
    <Inputs>
      <Argument helpKey="Filter of identities" name="filter" required="true" type="string">
        <Prompt>The filter</Prompt>
      </Argument>
      <Argument helpKey="The threshold" name="threshold" required="true" type="int">
        <Prompt>The threshold</Prompt>
      </Argument>
    </Inputs>
    <Returns>
      <Argument name="total" type="int">
        <Prompt>task_out_identity_refresh_total</Prompt>
      </Argument>
    </Returns>
  </Signature>
</TaskDefinition>

It shows like this in the UI:

In the code, you can use these arguments like so:

String filter = args.getString("filter");
int threshold = args.getInt("threshold");

Template

Your task could be a one-off, but you might want to consider using it as a template. By setting template="true" in the root of the TaskDefinition, it will appear in the drop-down of the New Task button, allowing your IIQ administrator to create a new instance of it:

Progess Updates

You are now ready to execute the Task. The newly created TaskDefinition ensures that the execute method is called. In that method, you can do whatever you want (make coffee :slight_smile: !), but I would recommend regularly providing updates on the progress. You do this by updating the taskResult that you receive.

For example, if you update 10,000 objects via an Iterator, you can keep a counter and regularly update the status so that the end user knows how long it will take for the task to complete: ‘Processing John Doe (30 of 10,000)’.

@Override
public void execute(SailPointContext context, TaskSchedule taskSchedule, TaskResult taskResult, Attributes<String, Object> args) throws Exception {
  QueryOptions qo = new QueryOptions();
  qo.setCloneResults(true);
  
  try {
    int totalCount = context.countObjects(Identity.class, qo);
    int count = 0;
    
    Iterator<Identity> it = context.search(Identity.class, qo);
    
    while (it.hasNext()) {
      count++;
      
      Identity identity = it.next();
      
      //saving the progress to the TaskResult object
      if((count % 10) == 0) {
        taskResult.setProgress(String.format("Processing %s (%s of %s)", identity.getDisplayName(), count, totalCount));
        context.saveObject(taskResult);
        context.commitTransaction();
      } 

      context.decache(identity);
    }

    Util.flushIterator(it);
  } catch (Exception ex) {
    logger.error("Exception occurred: " + ex.getMessage());
  }
}

This is how that looks from the UI:

When the Task is Complete

It is possible to indicate whether the execution of the Task was successful. This can be done using the setCompletionStatus method in the taskResult. This can be: Success, Warning, Error, Terminated, or TempError. In the Task configuration, the end user can now choose to send an email to an administrator when the task fails, like in any other task.

taskResult.addAttribute("total", Util.otos(count));
taskResult.addMessage(new Message(Message.Type.Error, "Your custom reason why this task is in error!"));
taskResult.setCompletionStatus(TaskResult.CompletionStatus.Error);

If you really want to pull out all the stops, you can add return values to your task definition signature. When you write back an attribute with the same name from your code, it will be neatly displayed on the summary page.

Task definition:

  <Signature>
    <Returns>
      <Argument name="total" type="int">
        <Prompt>task_out_identity_refresh_total</Prompt>
      </Argument>
    </Returns>
  </Signature>

Code:

taskResult.addAttribute("total", Util.otos(count));

Result:

Terminating the Task Early

Imagine you are iterating through 10,000 objects, but as an end user, you notice something is wrong. You want to be able to terminate the Task via the UI. You will need to implement this in your code. When terminating the Task, IIQ signals this by calling the terminate method. I would advise using a global boolean variable (terminate). In the terminate method, set this to true and check it in your Iterator. Once it is true, exit the Iterator (don’t forget to flush to free up memory!!). The task will then end.
Code of the terminate method:

@Override
public boolean terminate() {
  terminate = true;
  return false;
}

How you handle this in the execute method (please note the !terminate addition to the while loop):

while (it.hasNext() && !terminate) {
  count++;
  
  Identity identity = it.next();
  
  if((count % 10) == 0) {
    taskResult.setProgress(String.format("Processing %s (%s of %s)", identity.getDisplayName(), count, totalCount));
    context.saveObject(taskResult);
    context.commitTransaction();
  }

  context.decache();
}

Util.flushIterator(it);

Conclusion

I hope you have found this guide to developing an IdentityIQ Task definition useful. In a future blog, I will gladly delve into partitioning, as there is much to explore there!

2 Likes