Storing Extended Attributes in System Configuration

Customers usually have different deployment environments such as production, testing and development. The names and number of the environments may vary of course. If you want to maintain the configuration in XML files for revision control, you will most probably need to maintain multiple copies of (almost) the same configuration, mappings etc. Can we do it better?

In some cases, the number of different configuration is reduced by using domain names instead of IP addresses of resources, if the customer maintains different DNS servers. So I would not need to have 3 different resource XML files saved in my revision control system, if the only difference would be the IP address and the customer uses the same DNS name for all instances of the resource.

But mostly you are not so lucky. The environments differ in other parameters than just domain names. The production, test and devel Directory server resources may differ in the directory tree structure (suffix, additional containers) or even in some naming conventions. Even the attributes in midPoint can be generated differently for each environment causing a need of having multiple Object Templates and carefully importing the correct one into each environment. It would be great to have an option to have one set of mappings and use conditions based on the current environment to decide what the mapping should do. For me, for example, it was going to be a very difficult to maintain three object templates for each environment, multiple copies of the same resource, and even tens/hundreds of roles.

But this is midPoint. Extensible, configurable, lean, mean provisioning machine, you know.

MidPoint can extend the attribute schema of any object. So I’ve decided to extend “System Configuration” object by “customerEnv” attribute and use this attribute in the mappings. Of course I would need to maintain different copies of System Configuration, or just mention the setting in the deployment procedure for each environment. But chances are, the number of copies of other objects will be reduced/mitigated.

So, what will we need:

  1. Custom Schema Extension definition, prepared as in Custom Schema Extension wiki page
  2. Extend the System Configuration object – in each environment
  3. Special $configuration variable (populated by midPoint)
  4. objects containing mappings and expressions such as Object Template, Resource and Role

First, let’s create/update the Custom Schema Extension with the new “customerEnv” (string, single value) attribute:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xsd:schema elementFormDefault="qualified"
  <xsd:complexType name="SysconfigExtensionType">
    <xsd:annotation>
      <xsd:appinfo>
        <a:extension ref="c:SystemConfigurationType"/>
      </xsd:appinfo>
    </xsd:annotation>
    <xsd:sequence>
  <xsd:element name="customerEnv" type="xsd:string">
    <xsd:annotation>
      <xsd:documentation>
Contains DEV | TEST | PROD etc. to be used in mappings to generate suffix etc.
      </xsd:documentation>
    </xsd:annotation>
  </xsd:element>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

Save this file as “extension-schema.xsd” and put the file to the midpoint.home/schema directory. Then restart midPoint.

Now extend the System Configuration object in each environment. You can use the Configuration – Repository object interface and edit the object as XML:

1
2
3
4
5
6
7
8
9
                     oid="00000000-0000-0000-0000-000000000001"
                     version="91">
   <name>SystemConfiguration</name>
   <extension xmlns:customer="https://evolveum.com/partner/customer">
      <customer:customerEnv>TEST</customer:customerEnv>
      <!-- Values: DEV / TEST / PROD -->
   </extension>
. . .

See how our custom “customerEnv” attribute is used to set the value; do not forget the namespace.

Now you can refer to this value in mappings using the midPoint $configuration variable, for example in the default user template. Let’s create the user name in midPoint automatically using:

  • givenName (normalized)
  • familyName (normalized)
  • suffix based on environment (such as ‘@example.com’ for production, and ‘xxx@example.com’ for other environments)

The object template (simplified just for this purpose) will generate the name. See the “$configuration/extension/customer:customerEnv” variable being used as one of the sources of the mapping; do not forget the namespace. Also you can see how the values of Given Name and Family Name are normalized (standard Script Expression Functions) and how spaces are removed (I didn’t want to have spaces in username) by using Groovy’s “tr”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<objectTemplate oid="00000000-1100-1100-0002-000000000001"
    <name>My User Template</name>
    <mapping>
        <name>My Object Template: Name for employees</name>
        <source>
            <path>givenName</path>
        </source>
        <source>
            <path>familyName</path>
        </source>
        <source>
            <path>employeeType</path>
        </source>
        <source>
            <path>$configuration/extension/customer:customerEnv</path>
        </source>
        <expression>
            <script>
                <code>
// All given/family names used: "Anne  Emma Smith Smythe" -> anne.emma.smith.smythe@...
// All given/family names used: "Anne  Emma Smith-Smythe" -> anne.emma.smithsmythe...
tmpGivenName = basic.norm(basic.stringify(givenName))?.tr(' ', '.')
tmpFamilyName = basic.norm(basic.stringify(familyName))?.tr(' ', '.')
suffix = ''
if (!basic.isEmpty(customerEnv)) {
    if (customerEnv == 'PROD') suffix = '@example.com'
    else suffix = '@' + basic.lc(customerEnv) + '.example.com'
    tmpGivenName + '.' + tmpFamilyName + suffix
    }
                </code>
            </script>
        </expression>
        <target>
            <path>name</path>
        </target>
        <condition>
                <script>
                        <code>givenName != null &amp;&amp; familyName != null &amp;&amp; employeeType == 'EMPLOYEE'</code>
                </script
        </condition>
    </mapping>
. . .

In the above configuration, username for “John Smith” would be: “john.smith@test.example.com”. So you can have one object template for users in all environments instead of three (production, testing, development).

Leave a Reply

Your email address will not be published. Required fields are marked *