Skip to main content

Chef It Up: OpenDJ

Recently I had a pleasure of automating the installation of OpenDJ, a ForgeRock Directory (think LDAP) project. While I can't share the cookbook we ended up using, I can provide the most important part.


A project by ForgeRock implementing LDAPv3 server and client. This is a fork of abandoned Sun OpenDS.
A command line application allowing you to search LDAP directory.
A command line application that modifies the contents of LDAP directory record(s)


You all know LWRPs are good for you. Yet, lots of people writing Chef cookbooks tend to ignore that and come up with something like this:

execute 'run ldapmodify' do
    command <<-eos
        ldapmodify blah
        echo > /tmp/.ldapmodify-ran
    not_if { ::File.exist? '/tmp/.ldapmodify-ran' }

This is bad because...

  • In case you modify anything in the referenced command, your execute block will not run, because of the guard file.
  • Every time you need to add another ldapmodify command, you will need to come up with another guard name.
  • It enforces idempotency not based on the data, but the side effect. And when you converge the node for the second time after removing the data, you won't get it back.

At some point you will have something like this:


This does not scale well.

Well, initial configuration of OpenDJ really requires something like the execute block above - the installer can run only once, so there should be a guard on the configuration files it creates.

What to do then?

Add ldapmodify resource!

You will need to create a library and a resource/provider pair. The library will implement the ldapsearch shellout, while provider will simply call it. Having the code in a library allows you to turn that ugly execute block into a less ugly opendj_ldapmodify one:

opendj_ldapmodify '/path/to.ldif' do
    only_if { !ldapsearch('ou=something').include?('ou: something') }

ldapsearch returns error when it can't connect to the server/baseDN is wrong, yet it returns 0 when there are no results. That makes perfect sense, but since we need a flexible call, we simply analyze the output to see whether it contained the object we were looking for.

I don't consider myself to be a ruby developer, so take the code with a grain of salt:

module OpenDjCookbook
  module Helper
    include Chef::Mixin::ShellOut

    def ldapsearch(query)
      config = node['opendj']
      cmdline = [
        '-b', config['basedn'],
        '-h', config['host'],
        '-p', config['port'], # make sure this is a string
        '-D', config['userdn'],
        '-w', config['password'],

        shell_out!(cmdline, :user => config['service_user']).stdout.strip
      rescue Mixlib::ShellOut::ShellCommandFailed => e

-p argument needs to be a string because of mixlib-shellout#90, and yes, I am using password from a node attribute (use run_state instead!). In case of error we simply return an empty string to make not_if/only_if simpler.


ldapmodify provider is quite similar, we just shellout to ldapmodify. We assume the path to file will be the resource name, so we just do this:


action :run do
  path = new_resource.path

  execute "ldapmodify -f #{path}" do
    config = node['opendj']
    command [
      '-a', # default action: add
      '-h', config['host'],
      '-p', config['port'].to_s, # must be string
      '-D', config['userdn'],
      '-w', config['password'],
      '-f', path

    user config['service_user']


And resource is very simple:

actions: run
default_action :run

attribute :path, :name_attribute => true, :kind_of => String, :required => true

Hooking up a library to resource

You won't be able to use ldapsearch if you don't include the library into a resource, and I found that linking it in the recipe works for me, so...

# opendj/recipes/default.rb
Chef::Resource::OpendjLdapmodify.send(:include, OpendjCookbook::Helper)

# rest of the recipe


So now we can call ldapmodify from chef as a resource, and use ldapsearch to avoid modifying the data store when we don't need to.

OpenDJ is quite an interesting project, you may think that LDAP is dead, but it is alive and kicking. A lot of companies use ForgeRock solutions for identity and access management and the identity part most likely lives in a LDAP database. Apache Directory Project is another implementation of LDAPv3 server, an Eclipse-based Directory Studio for manipulating the data.


Comments powered by Disqus