Wednesday, June 16, 2010

The IBM Tivoli Identity Manager API - Jythonised

I wanted to build a new ITIM environment recently and figured it was about time I started to put together a proper set of Jython scripts to help me automate the process. Below, I've detailed my thinking behind a Jython script which will be capable of taking a delimited file defining an Organisational Structure, and load it into ITIM using ITIM APIs and the Jython scripting framework.

My virtual environment for this purpose was:
  • Windows 2003 R2 Standard Edition Service Pack 2
  • IBM WebSphere Application Server v7.0.0.9
  • IBM Tivoli Identity Manager v5.1.0.0
  • APISCRIPT v5.0.0.4 (available from OPAL)

The Setup
The apiscript tool (v5.0.0.4) should be downloaded from the OPAL site and deployed as per the instructions. For example,  the following files need to be configured to match the ITIM environment being managed:
  • etc/host/{hostname}.properties
  • bin/env_master.bat

But because I'm using an ITIM v5.1 system, I need to also make a "tweak" to the apiscript.bat (or apiscript.ksh) file as the structure of the extensions directory has been updated in this release. I need to include a 5.1 directory between extensions and examples as such:

set APISCRIPT_LOGIN_CONFIG=%APISCRIPT_ITIM_HOME%\extensions\5.1\examples\apps\bin\jaas_login_was.conf

Input File
The following input file is to be used to define the organisational structure:
ParentOrgUnit|OrgUnit|
|internal|
|external|
internal|unit1|
internal|unit2|
external|unit3|
unit2|unit4|
unit2|unit5|
unit10|unit7|
unit4|unit6|
external|unit4|
external|unit8|
external|unit8|
unit8|unit9|
unit9|unit8|

Each line was terminated with a | character because early in the testing process I noticed that carriage return/line feed characters were making their way into the ITIM environment.

The Code

The code required to process such an input file should be broken down into a number of sections:


Section 1: Process The Command Line Arguments
In order to ensure that the script can process a variety of input files, the file to be processed should be passed as a command line argument. An additional argument is being processed here to enable verbose logging to take place:
try:
   opts, args = getopt.getopt(sys.argv[1:], "qi:", ["inputfile="])
except getopt.GetoptError, err:
   print str(err)
   usage()
   sys.exit(2)

inputfile = ""
quietmode = "false"

for opt, arg in opts:
   if opt == "-q":
      print "Quiet mode enabled"
      quietmode = "true"
   elif opt == "-i":
      inputfile = arg
Section 2: Read the Input File
The processing of the input file should be wrapped in a while loop and each line processed should be split into its constituent parts using the split method on the line object:
infile = open(inputfile,"r")
while infile:
   line = infile.readline()
   if not line: break
   items=line.split("|")
   parentorg=items[0]
   org=items[1]
At this point, we now have an Organisational Unit Name and a Parent Organisational Unit Name ready for placement in the ITIM data model.

Section 3: Process Each Entry
For each record retrieved above, we need to determine that the Parent Organisational Unit Name actually exists in the data model. If it does not, we cannot process the entry. If it does exist, we need to check that the child OU doesn't already exist. If it also exists, then there is no point in continuing processing of this entry, otherwise we need to get an OrganizationalUnitMO object for the Parent OU to enable us to create an OU using the child Organisational Unit Name.
if parentorg == 'ParentOrgUnit':
   # Do Nothing - it's the header
   logit('Processing Header')
else:
   logit('**********************************************')
   logit('Processing ' + org)

   if org_exists(parentorg) != 'False':
      if org_exists(org) != 'False':
         logit('Child OU already exists:' + org)
      else:
         # Create OU
         logit('Child OU does not exist:' + org)
         parent_org_mo = get_org_mo(parentorg)
         orgchart.do_create_ou_from_args(org, parent_org_mo)
      # end if loop
   else:
      logit('Parent Org Unit does not exist:' + parentorg)
   # end if loop
# end if
Section 4: Check That An OU Exists
To check that an OU exists, we need to perform a search using a SearchMO object for an ORGUNIT object using a filter based on the OU name provided in the data file. If an object is found, we should return TRUE, else return FALSE:
def org_exists(orgunit):
   foundit = 'False'
   if orgunit == "":
      foundit = 'true'
   else:
      def_org_cont_mo = orgchart.get_default_org_mo()
      myPlatform =
apiscript.util.get_default_platform_ctx_and_subject()
      search_mo = SearchMO(myPlatform)
      search_mo.setCategory(ObjectProfileCategoryConstant.ORGUNIT)
      myFilter = "(ou=" + orgunit + ")"
      search_mo.setFilter(myFilter)
      search_mo.setScope(SearchParameters.SUBTREE_SCOPE)
      search_mo.setContext(def_org_cont_mo)
      results_mo = search_mo.execute()
      for result in results_mo.getResults():
         if orgunit == result.name:
            foundit = 'True'
   return foundit
# end org_exists
Section 5: Get the MO Of An OU
An OrganizationalUnitMO object can be generated after searching for an OU by performing two additional functions:
  • getDistinguishedName on the search result
  • create_org_container_mo using the DN returned from the above method
def get_org_mo(orgunit):
   if orgunit == "":
      org_mo = orgchart.get_default_org_mo()
   else:
      def_org_cont_mo = orgchart.get_default_org_mo()
      search_mo = SearchMO( *apiscript.util.get_default_platform_ctx_and_subject())
      search_mo.setCategory(ObjectProfileCategoryConstant.ORGUNIT)
      myFilter = "(ou=" + orgunit + ")"
      search_mo.setFilter(myFilter)
      search_mo.setScope(SearchParameters.SUBTREE_SCOPE)
      search_mo.setContext(def_org_cont_mo)
      results_mo = search_mo.execute()
      for result in results_mo.getResults():
         if orgunit == result.name:
            mydn = result.getDistinguishedName()
            org_mo = orgchart.create_org_container_mo(mydn)
   return org_mo
# End get_org_mo
The Result
The output from the script is:
C:\work\apiscript-5.0.0.4\apiscript>c:\work\apiscript-5.0.0.4\apiscript\bin\apiscript.bat -f c:\work\apiscript-5.0.0.4\apiscript\py\orgs.py -z -i c:\\work\\apiscript-5.0.0.4\\apiscript\\data\\orgs.dat
Using master environment: "C:\work\apiscript-5.0.0.4\apiscript\bin\env_master.bat"
Using custom BIN_HOSTNAME: "stephen-w0ckd5b"
Using custom ETC_HOSTNAME: "stephen-w0ckd5b"
Using host properties: "C:\work\apiscript-5.0.0.4\apiscript\etc\host\stephen-w0ckd5b.properties"
Using APISCRIPT_WAS_HOME: "C:\Program Files\IBM\WebSphere\AppServer"
Using APISCRIPT_ITIM_HOME: "C:\Program Files\IBM\itim"
WASX7357I: By request, this scripting client is not connected to any server process. Certain configuration and application operations will be available in local mode.
Welcome to IBM Tivoli Identity Manager API Scripting Tool (apiscript) version: 5.0.0.4
Setting system property: java.security.auth.login.config
Setting com.ibm.CORBA properties: loginSource, loginUserid, loginPassword
WASX7303I: The following options are passed to the scripting environment and are available as arguments that are stored in the argv variable: "[-z, -i, c:\\work\\apiscript-5.0.0.4\\apiscript\\data\\orgs.dat]"
Logging configuration file is not found. All the logging information will be sent to the console.
**********************************************
Input file selected is c:\work\apiscript-5.0.0.4\apiscript\data\orgs.dat
Processing Header
**********************************************
Processing internal
Child OU does not exist:internal
**********************************************
Processing external
Child OU does not exist:external
**********************************************
Processing unit1
Child OU does not exist:unit1
**********************************************
Processing unit2
Child OU does not exist:unit2
**********************************************
Processing unit3
Child OU does not exist:unit3
**********************************************
Processing unit4
Child OU does not exist:unit4
**********************************************
Processing unit5
Child OU does not exist:unit5
**********************************************
Processing unit7
Parent Org Unit does not exist:unit10
**********************************************
Processing unit6
Child OU does not exist:unit6
**********************************************
Processing unit4
Child OU already exists:unit4
**********************************************
Processing unit8
Child OU does not exist:unit8
**********************************************
Processing unit8
Child OU already exists:unit8
**********************************************
Processing unit9
Child OU does not exist:unit9
**********************************************
Processing unit8
Child OU already exists:unit8

C:\work\apiscript-5.0.0.4\apiscript>
Visually, this gets represented in ITIM as:


In conclusion, I'm not a Jython/Python sripting guru. Indeed, this was my first foray into Python. It is, however, a relatively straightforward language to learn and can be a very powerful tool in your ITIM Administrative toolset.

The full script can be downloaded at downloads/loadous.zip.