Infrastructure at your Service

In article A Small Footprint Docker Container with Documentum command-line Tools, we presented a possible way to containerize the usual Documentum clients idql, iapi, dmawk and dmdocbroker. Those are command-line tools mostly used in batch processing or for one-of-its-kind manual tasks such as investigating or troubleshooting. OpenText also offers Documentum Administrator (aka DA), a java WDK-based client which is very useful for the occasional manual tasks. With its GUI running in a browser, it gives a 2-dimensional, point-and-click view of a repository’s objects. As it is so useful, let’s add it into our transportable toolbox. Ideally, it should be included in the aforementioned container, which we did, but at more than 1 Gb for DA alone, the footprint is now much larger, almost by a factor 3 ! We tried to keep it small as much as possible by using an existing remote global registry repository but the image’s size is still slightly below 1 Gb now. Anyway, traditional storage is cheap nowadays and the idea to have a complete installation of DA in just a few minutes as many times as we want is a tremendous incentive, so let’s see how we did it.

Overview

Since we want to enhance our Documentum clients’ toolbox, it makes sense to incrementally install DA in the same image. As this image already embeds a JDK, there will be no need to install it and the application server and DA will use the clients’ one. Remember that all those clients are DFCs programs and therefore they need a JVM to run. An alternative solution would be to containerize DA separately from the command-line clients; in such a choice, java shall be installed as a top-most pre-requisite.
In order to keep the image’s size as small as possible, and also to avoid any additional licensing issue, we will deploy DA v16.4 on the open source tomcat application server. Tomcat’s installation is straightforward: just uncompress its tarball in its destination directory. Deployment of DA is similarly simple: extract its war file’s content in a directory, say da, under tomcat’s webapps sub-directory. Once done, configuration must be done by editing several files according to the OpenText™Documentum® Platform and Platform Extensions Version 16.4 Installation Guide and OpenText™ Documentum® Web Development Kit and Webtop Deployment Guide. Of course, since we are building images, all these steps will be scripted.
DA requires a global registry repository to store preferences and other settings. While this dependency looks superficial and non essential to its working, it is mandatory or DA won’t work at all if that repository is missing. Here are the related error messages output in the catalina.out log file:

21:01:04,494 ERROR [localhost-startStop-1] com.documentum.web.common.Trace - Preference repository is not available, reason: Preference Repository is not reachable.
com.documentum.web.env.PreferenceRepository$DisabledException: Preference Repository is not reachable.
at com.documentum.web.env.PreferenceRepository.initialize(PreferenceRepository.java:306)
at com.documentum.web.env.PreferenceRepository.(PreferenceRepository.java:217)
at com.documentum.web.env.PreferenceRepository.(PreferenceRepository.java:61)
at com.documentum.web.env.PreferenceRepository$ApplicationListener.notifyApplicationStart(PreferenceRepository.java:76)
...

A nastier one, whose relationship with a missing global registry repository is less than obvious, is also output later:

21:01:30,299 ERROR [http-nio-8080-exec-8] com.documentum.web.common.Trace - Encountered error in error message component jsp
java.lang.NullPointerException
at com.documentum.web.formext.config.ConfigThemeResolver.getTheme(ConfigThemeResolver.java:167)
at com.documentum.web.formext.config.ConfigThemeResolver.getStylesheets(ConfigThemeResolver.java:207)
at com.documentum.web.common.BrandingService$ThemeResolverWrapper.getStylesheets(BrandingService.java:257)
at com.documentum.web.form.WebformIncludes.renderStyleSheetIncludes(WebformIncludes.java:175)
...

Apparently, themes are also stored in that repository, or depend on information in it. Anyway, in order to ease up the image creation, we will use an existing remote repository from another container. In the alternative, stand-alone DA solution, the global registry would be installed along DA in the same container.
All the containers are linked here through a docker overlay network because they sit on distinct hosts.

The dockerfile

Here it is:

# syntax = docker/dockerfile:1.0-experimental
# we are using the secret mount type;
# cec - dbi-services - August 2019

FROM dbi/dctm-clients:v1.0
MAINTAINER "cec"

ARG soft_repo
ARG INSTALL_OWNER
ARG INSTALL_BASE
ARG INSTALL_HOME
ARG INSTALL_TMP
ARG INITIAL_DOCBROKER
ARG INITIAL_DOCBROKER_PORT
ARG DOCBASE_ADMIN
ARG DOCUMENTUM
ARG DOCUMENTUM_OWNER
ARG GLOBAL_REGISTRY
ARG JAVA_HOME
ARG TOMCAT_VERSION
ARG CATALINA_BASE
ARG CATALINA_HOME
ARG CATALINA_TMPDIR
ARG JRE_HOME
ARG CLASSPATH

USER root
RUN sed --in-place --regexp-extended -e 's|(%wheel)|\# \1|' -e 's|^#+ *(%wheel[^N]+NOPASSWD)|\1|' /etc/sudoers && \
    useradd --shell /bin/bash --home-dir /home/${INSTALL_OWNER} --create-home ${INSTALL_OWNER} && usermod -a -G wheel ${INSTALL_OWNER}

# set the $INSTALL_OWNER's password passed in the secret file;
RUN --mount=type=secret,id=dctm-secrets,dst=/tmp/dctm-secrets . /tmp/dctm-secrets && echo ${INSTALL_OWNER}:"${INSTALL_OWNER_PASSWORD}" | /usr/sbin/chpasswd

RUN mkdir -p ${INSTALL_TMP} ${CATALINA_BASE}                                                   && \
    chown -R ${INSTALL_OWNER}:${INSTALL_OWNER} ${INSTALL_HOME} ${INSTALL_TMP} ${CATALINA_BASE} && \
    chmod -R 755 ${INSTALL_HOME} ${INSTALL_TMP} ${CATALINA_BASE}

# copy the binary files;
COPY ${soft_repo}/da.war ${soft_repo}/DA_16.4.0120.0023.zip ${soft_repo}/${TOMCAT_VERSION}.tar.gz ${soft_repo}/docbrokers.tar ${INSTALL_TMP}/.
COPY ${soft_repo}/entrypoint.sh ${INSTALL_HOME}/.
RUN chown ${INSTALL_OWNER}:${INSTALL_OWNER} ${INSTALL_TMP}/* ${INSTALL_HOME}/entrypoint.sh && chmod u=rwx,g=rx,o=r ${INSTALL_HOME}/entrypoint.sh

# make the CLI comfortable again;
USER ${INSTALL_OWNER}
RUN echo >> ${HOME}/.bash_profile                                                                               && \
    echo "set -o vi" >> ${HOME}/.bash_profile                                                                   && \
    echo "alias ll='ls -alrt'" >> ${HOME}/.bash_profile                                                         && \
    echo "alias psg='ps -ef | grep -i'" >> ${HOME}/.bash_profile                                                && \
    echo "export JAVA_HOME=${JAVA_HOME}" >> ${HOME}/.bash_profile                                               && \
    echo "export TOMCAT_VERSION=${TOMCAT_VERSION}" >> ${HOME}/.bash_profile                                     && \
    echo "export CATALINA_BASE=${CATALINA_BASE}" >> ${HOME}/.bash_profile                                       && \
    echo "export CATALINA_HOME=${CATALINA_HOME}" >> ${HOME}/.bash_profile                                       && \
    echo "export CATALINA_TMPDIR=${CATALINA_TMPDIR}" >> ${HOME}/.bash_profile                                   && \
    echo "export JRE_HOME=\${JAVA_HOME}/jre" >> ${HOME}/.bash_profile                                           && \
    echo "export CLASSPATH=\${CATALINA_HOME}/bin/bootstrap.jar:\${CATALINA_HOME}/bin/tomcat-juli.jar" >> ${HOME}/.bash_profile && \
    echo "export PATH=.:\${CATALINA_HOME}/bin:\${JAVA_HOME}/bin:\$PATH" >> ${HOME}/.bash_profile                && \
    echo >> ${HOME}/.bash_profile

# install tomcat and configuration as per the OpenText™Documentum® Platform and Platform Extensions Version 16.4 Installation Guide;
USER ${INSTALL_OWNER}
RUN tar xvf ${INSTALL_TMP}/${TOMCAT_VERSION}.tar.gz --directory ${CATALINA_BASE}/../. && \
    sed --in-place --regexp-extended "$ a \\\n# custo for DA\norg.apache.jasper.compiler.Parser.STRICT_WHITESPACE=false\njnlp.com.rsa.cryptoj.fips140loader=true" ${CATALINA_HOME}/conf/catalina.properties && \
    sed --in-place --regexp-extended -e "/<load-on-startup>3<\/load-on-startup>/i <init-param\>\\n   <param-name>enablePooling<\/param-name>\\n   <param-value>false<\/param-value>\\n<\/init-param>" -e "s|(<session-timeout>)[0-9]+|\11440|" -e "/<session-config>/a <cookie-config>\\n<http-only>false<\/http-only>\\n<!--secure>true<\/secure-->\\n<\/cookie-config>" ${CATALINA_HOME}/conf/web.xml && \
    sed --in-place --regexp-extended "s|(<Context)|\1 useHttpOnly=\"false\"|" ${CATALINA_HOME}/conf/context.xml && \
    sed --in-place --regexp-extended "/<Connector port=\"8080\"/a compression=\"on\"\\ncompressionMinSize=\"2048\"\\ncompressableMimeType=\"text\/html,text\/xml,application\/xml,text\/plain,text\/css,text\/javascript,text\/json,application\/x-javascript,application\/javascript,application\/json\"\\nuseSendfile=\"false\"" ${CATALINA_HOME}/conf/server.xml && \
    touch ${CATALINA_HOME}/bin/setenv.sh && sed --in-place --regexp-extended '$ a JAVA_OPTS="-server -XX:+UseParallelOldGC -Xms256m -Xmx1024m"' ${CATALINA_HOME}/bin/setenv.sh && \
    sed --in-place --regexp-extended -e '/(<Valve .*)/i <\!--' -e '/<Manager /i -->' ${CATALINA_HOME}/webapps/manager/META-INF/context.xml && \
    sed --in-place --regexp-extended -e '/(<Valve .*)/i <\!--' -e '/<Manager /i -->' ${CATALINA_HOME}/webapps/host-manager/META-INF/context.xml && \
    sed --in-place --regexp-extended "/<\/tomcat-users>/i \ \ <role rolename=\"manager-gui\"\/>\\n\ \ <role rolename=\"manager-script\"\/>\\n\ \ <role rolename=\"manager-jmx\"\/>\\n\ \ <role rolename=\"manager-status\"\/>\\n\ \ <user username=\"tomcat\" password=\"tomcat\" roles=\"manager-gui,admin-gui,admin-script,manager-script,manager-jmx,manager-status\"\/>" ${CATALINA_HOME}/conf/tomcat-users.xml && \
    tar xvf ${INSTALL_TMP}/docbrokers.tar --directory ${CATALINA_BASE}/webapps/.

# deployment of da.war and edition of WEB-INF/classes/dfc.properties;
RUN . ${HOME}/.bash_profile && mkdir ${CATALINA_HOME}/webapps/da && cd ${CATALINA_HOME}/webapps/da && mv ${INSTALL_TMP}/da.war . && jar xvf ./da.war && rm ${CATALINA_HOME}/webapps/da/da.war && \
    mv ${INSTALL_TMP}/DA_16.4.0120.0023.zip ${CATALINA_HOME}/webapps/da && unzip -o DA_16.4.0120.0023.zip && rm DA_16.4.0120.0023.zip && \
    touch ${CATALINA_HOME}/webapps/da/WEB-INF/classes/dfc.properties && mkdir ${INSTALL_HOME}/dfc_data && \
    sed --in-place --regexp-extended -e "s|^(dfc\.data\.dir *=.*)|# \1|" -e "1s|^|dfc\.data\.dir=${INSTALL_HOME}\/dfc_data\\n|" -e "1s|^|#include ${DOCUMENTUM}/config/dfc.properties\\n|" -e "$ a dfc.docbroker.host[0]=${INITIAL_DOCBROKER}\\ndfc.docbroker.port[0]=${INITIAL_DOCBROKER_PORT}" -e '/^dfc\.diagnostics\.resources\.enable *=.*/d' -e '$ a dfc\.diagnostics\.resources\.enable=false' -e '/^dfc\.config\.check_interval *=.*/d' -e '$ a dfc\.config\.check_interval = 5' ${CATALINA_HOME}/webapps/da/WEB-INF/classes/dfc.properties && \
    sed --in-place --regexp-extended "s|(<session-timeout>)[0-9]+|\11440|" ${CATALINA_HOME}/webapps/da/WEB-INF/web.xml

USER root
RUN --mount=type=secret,id=dctm-secrets,dst=/tmp/dctm-secrets . /tmp/dctm-secrets; su - ${INSTALL_OWNER} -c ". \${HOME}/.bash_profile; cd \${CATALINA_HOME}/webapps/da; passwords=\"`cd \${CATALINA_HOME}/webapps/da ; \${JAVA_HOME}/bin/java -cp WEB-INF/classes:WEB-INF/lib/dfc.jar:WEB-INF/lib/commons-io-1.2.jar com.documentum.web.formext.session.TrustedAuthenticatorTool ${DMC_WDK_PREFERENCES_PASSWORD} ${DMC_WDK_PRESETS_PASSWORD}`\"; gawk -v passwords=\"\$passwords\" 'BEGIN {ind = 0; while (match(passwords, /Encrypted: \[[^]]+\]/)) {pass_tab[ind++] = substr(passwords, RSTART + 12, RLENGTH - 13); passwords = substr(passwords, RSTART + RLENGTH)}} \
{if (match(\$0, /<presets>/)) {print; while (getline > 0 && !match(\$0, /<password><\/password>/)) print; print \"<password>\" pass_tab[1] \"</password>\"} else if (match(\$0, /<preferencesrepository>/)) {print; while (getline > 0 && !match(\$0, /<password><\/password>/)) print; print \"<password>\" pass_tab[0] \"</password>\"} else print}' ${CATALINA_HOME}/webapps/da/wdk/app.xml > /tmp/app.xml; mv /tmp/app.xml ${CATALINA_HOME}/webapps/da/wdk/app.xml; \
    sed --in-place --regexp-extended -e '/<cookie_validation>.+$/{N;s|true|false|}' ${CATALINA_HOME}/webapps/da/wdk/app.xml; \
DM_BOF_REGISTRY_PASSWORD_ENCRYPTED=`cd ${CATALINA_HOME}/webapps/da; \${JAVA_HOME}/bin/java -cp WEB-INF/classes:WEB-INF/lib/dfc.jar com.documentum.fc.tools.RegistryPasswordUtils ${DM_BOF_REGISTRY_PASSWORD}`; \
    sed --in-place --regexp-extended \"$ a dfc.globalregistry.repository=${GLOBAL_REGISTRY}\\ndfc.globalregistry.username=${DM_BOF_REGISTRY_USER}\\ndfc.globalregistry.password=\${DM_BOF_REGISTRY_PASSWORD_ENCRYPTED}\" ${CATALINA_HOME}/webapps/da/WEB-INF/classes/dfc.properties" && \
    find ${INSTALL_HOME} -user root -exec chown -R ${INSTALL_OWNER}:${INSTALL_OWNER} {} \;

# cleanup;
USER root
RUN rm -r ${INSTALL_TMP} /tmp/dctm-secrets

# start tomcat and DA;
USER ${INSTALL_OWNER}
WORKDIR ${INSTALL_HOME}
ENTRYPOINT ["/app/tomcat/entrypoint.sh"]

# build the image;
# cat - <<'eot' | gawk '{gsub(/#+ */, ""); print}'
# export INSTALL_OWNER=tomcat
# export INSTALL_BASE=/app
# export INSTALL_HOME=${INSTALL_BASE}/tomcat
# export INSTALL_TMP=${INSTALL_BASE}/tmp
# export INITIAL_DOCBROKER=container02
# export INITIAL_DOCBROKER_PORT=1489
# export DOCBASE_ADMIN=dmadmin
# export DOCUMENTUM=${INSTALL_BASE}/dctm
# export DOCUMENTUM_OWNER=dmadmin
# export GLOBAL_REGISTRY=dmtest02
# export JAVA_HOME=${DOCUMENTUM}/java64/JAVA_LINK
# export TOMCAT_VERSION=apache-tomcat-9.0.22
# export TOMCAT_VERSION=apache-tomcat-8.5.43
# export CATALINA_BASE=${INSTALL_HOME}/${TOMCAT_VERSION}
# export CATALINA_HOME=${CATALINA_BASE}
# export CATALINA_TMPDIR=${CATALINA_HOME}/temp
# export JRE_HOME=${JAVA_HOME}/jre
# export CLASSPATH=${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar
# cp entrypoint.sh docbrokers.tar files/.
# time DOCKER_BUILDKIT=1 docker build --squash --no-cache --progress=plain --secret id=dctm-secrets,src=./dctm-secrets \
#  --build-arg INSTALL_OWNER=${INSTALL_OWNER}                                                       \
#  --build-arg INSTALL_BASE=${INSTALL_BASE}                                                         \
#  --build-arg INSTALL_HOME=${INSTALL_HOME}                                                         \
#  --build-arg INSTALL_TMP=${INSTALL_TMP}                                                           \
#  --build-arg INITIAL_DOCBROKER=${INITIAL_DOCBROKER}                                               \
#  --build-arg INITIAL_DOCBROKER_PORT=${INITIAL_DOCBROKER_PORT}                                     \
#  --build-arg DOCBASE_ADMIN=${DOCBASE_ADMIN}                                                       \
#  --build-arg DOCUMENTUM=${DOCUMENTUM}                                                             \
#  --build-arg DOCUMENTUM_OWNER=${DOCUMENTUM_OWNER}                                                 \
#  --build-arg bSetGRPassword=${bSetGRPassword}                                                     \
#  --build-arg GLOBAL_REGISTRY=${GLOBAL_REGISTRY}                                                   \
#  --build-arg JAVA_HOME=${JAVA_HOME}                                                               \
#  --build-arg TOMCAT_VERSION=${TOMCAT_VERSION}                                                     \
#  --build-arg CATALINA_BASE=${CATALINA_BASE}                                                       \
#  --build-arg CATALINA_HOME=${CATALINA_HOME}                                                       \
#  --build-arg CATALINA_TMPDIR=${CATALINA_TMPDIR}                                                   \
#  --build-arg JRE_HOME=${JRE_HOME}                                                                 \
#  --build-arg CLASSPATH=${CLASSPATH}                                                               \
#  --tag="dbi/dctm-clients-da:v1.0" --build-arg soft_repo=./files                                   \
#  .
# eot

# retag the image & cleanup unused images;
# docker tag <image-id> dbi/dctm-clients-da:v1.0
# docker system prune

# run the image and remove the container on exit;
# docker run -d --rm --hostname=container-clients --network=dctmolnet01 --publish=8080:8080 dbi/dctm-clients-da:v1.0

# run the image and keep the container on exit;
# docker run -d --name container-clients --hostname=container-clients --network=dctmolnet01 --publish=8080:8080 dbi/dctm-clients-da:v1.0

As said, we chose to base DA’s image upon the container-clients’ one to inherit its O/S and JDK.
On lines 8 to 25, the build parameters are received in ARG variables. Unlike ENV variables, those won’t persist in the image, which is fine as they are not needed after the build any more.
On line 29, the account tomcat, owner of the application server installation, is created. For convenience, the tomcat user is also a sudoer, which under Centos is easily achieved just by adding the user into the wheel group. On line 28, we also edit the sudoers file to allow a passwordless sudo command. On line 32, tomcat’s password is taken from the sourced secret file and set. Secrets is a docker’s useful, albeit still experimental, feature aimed at passing confidential information to the build without leaving any trace in the intermediary images, which is ideal for passwords.
Furthermore, see line 67 to 69, the user tomcat has also been given full access to all of the tomcat’s Web applications, such as the manager and the host-manager; there is no reason not to.
On line 39, the binary packages are copied from a local sub-directory into the image. Here is the working directory’s layout:

[email protected]:~/builds/documentum/da$ ll -R
total 72
-rw-rw-r-- 1 dmadmin dmadmin 459 Aug 25 13:45 dctm-secrets
-rw-rw-r-- 1 dmadmin dmadmin 150 Aug 28 22:58 entrypoint.sh
-rw-rw-r-- 1 dmadmin dmadmin 17451 Sep 1 14:09 Dockerfile
drwxrwxr-x 2 dmadmin dmadmin 4096 Sep 4 13:46 files
-rw-rw-r-- 1 dmadmin dmadmin 20480 Sep 6 13:47 docbrokers.tar
 
./files:
total 131852
-rw-rw-r-- 1 dmadmin dmadmin 9717059 Aug 20 11:44 apache-tomcat-8.5.43.tar.gz
-rw-rw-r-- 1 dmadmin dmadmin 114358079 Aug 27 17:45 DA_16.4.0120.0023.zip

The files sub-directory only contains the minimum binaries’s archives to install into the image.
One line 70, the application docbrokers is deployed. More on this later.
On lines 73 to 77, DA is deployed and the recommended customizations are applied. Note on line 76 the #include statement of the client’s dfc.properties file. The reason will be explained later, although you can already figure out why (think to widql, etc …). On line 77, the session timeout is also increased to one day for it is really annoying and stupid to have to authentify 30 times in a day in each application while investigating; doing that at the workstation level is plenty enough.
One lines 80 and 81, the presets and preference accounts’ passwords are encrypted and inserted directly in wdk/app.xml. Documentum however recommends to copy and edit the relevant sections into custom/app.xml, which would be a better practice. Just don’t tell anyone.
On lines 83 to 84, the same is done for the global registry’s user dm_bof_registry. All those accounts passwords must have been set preliminarily in the global registry repository. Also, they are stored as clear-text values in the secret file which is passed to the build. Here is an example of the secret file used here:

dmad[email protected]:~/builds/documentum/da$ cat dctm-secrets
export INSTALL_OWNER=tomcat
export INSTALL_OWNER_PASSWORD=tomcat
 
export DMC_WDK_PREFERENCES_USER=dmc_wdk_preferences_owner
export DMC_WDK_PREFERENCES_PASSWORD=dmc_wdk_preferences_password
 
export DMC_WDK_PRESETS_USER=dmc_wdk_presets_owner
export DMC_WDK_PRESETS_PASSWORD=dmc_wdk_presets_password
 
export DM_BOF_REGISTRY_USER=dm_bof_registry
export DM_BOF_REGISTRY_PASSWORD=dm_bof_registry
 
export DOCBASE_ADMIN=dmadmin
export DOCBASE_ADMIN_PASSWORD=dmadmin

While presets and preference accounts’ passwords must be encrypted using the installed DA classes (and therefore received in clear text form), dm_bof_registry’s password could have been passed in encrypted form since it was already encrypted during the content server’s installation and stored in its local dfc.properties file. On the security-side however this would have made no difference because its encrypted password can be used interchangeably with the clear-text password to login ! So instead of introducing an asymetry here, we chose to (re-)encrypt all the passwords in one place using their respective tools (yes, they use different encryption algorihms and programs).
Note the mess on some RUN lines: nobody sane in their mind would do it that way but we just wanted to push it to the extreme with quoting and escaping on the RUN command-line, a kind of challenge, especially with a user account switch in between: being root is necessary to access the secrets but the rest must be done as user tomcat inside one long quoted string of commands. The complexity also comes from the need to access dockerfile’s environment variables along with the on-line script’s; the latter must be escaped. Strangely enough, even gawk script’s internal $0 variable must be escaped despite the script is bracketed inside single quotes. Normal persons would use an external bash script invoked by the RUN statements. Let’s hope that the HTML rendition of the dockerfile for the article did not skip special characters such as the < and >.
On line 85, we restore proper ownership for some files that inexplicably passed under the root ownership and on line 89 the mount point of the secret file is removed.
On line 94, we define an entrypoint for starting the tomcat server. Here is its content:

[email protected]:~/builds/documentum/da$ cat entrypoint.sh 

#!/bin/bash
. /home/tomcat/.bash_profile
${CATALINA_BASE}/bin/startup.sh
tail -F ${CATALINA_HOME}/logs/catalina.out

Note the -F option on line 6; we follow the log file by name so this will work even after a log file rotation (by default, every 24 hours).
After the web application server is started, the running container will just tail on the main log to stdout. The command

docker logs --follow --timestamps <da-container>

can then be used to follow the container’s stdout from outside the container.
Line 97 to 137 explain how to build the image. See the next paragraph for doing this.
Line 116 copies the entrypoint.sh script and the docbrokers application into the sub-directory from which the dockerfile will import files into the image.
Note on line 142 the prune command. This is very useful to remove the unused images and build caches, along with other objects, which reclaims lots of Gb. We wish we knew this command earlier, it would have spared us lots of volume enlargments sessions in Virtual Box/Gparted ! As it also removes stopped containers, be sure this is acceptable to you. We reckon docker containers are meant to be easily disposable and recreated but sometimes it takes time (e.g. about haft an hour for a containerized repository) and it can be annoying to unvoluntarily destroy such containers.
Lines 145 and 148 show two ways to run the image, either as an ephemeral container or as a permanent container. This is discussed later in it own paragraph.

Building the image

In order to extract the statements from the dockerfie, copy/paste into a shell prompt the text starting at the “cat” command down to the “.” (lines 97 to 137 from the dockerfile) included and add a “eot” here-document terminator on a new line, then copy/paste all the generated statements at a prompt. Those statements could has well be wrapped into a bash script if wished so. Or just recalled from the command-line’s history if some cycles of adjustments and testings are necessary.
Here are the build’s statements:

export INSTALL_OWNER=tomcat
export INSTALL_BASE=/app
export INSTALL_HOME=${INSTALL_BASE}/tomcat
export INSTALL_TMP=${INSTALL_BASE}/tmp
export INITIAL_DOCBROKER=container02
export INITIAL_DOCBROKER_PORT=1489
export DOCBASE_ADMIN=dmadmin
export DOCUMENTUM=${INSTALL_BASE}/dctm
export DOCUMENTUM_OWNER=dmadmin
export GLOBAL_REGISTRY=dmtest02
export JAVA_HOME=${DOCUMENTUM}/java64/JAVA_LINK
export TOMCAT_VERSION=apache-tomcat-8.5.43
export CATALINA_BASE=${INSTALL_HOME}/${TOMCAT_VERSION}
export CATALINA_HOME=${CATALINA_BASE}
export CATALINA_TMPDIR=${CATALINA_HOME}/temp
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar
cp entrypoint.sh docbrokers.tar files/.
time DOCKER_BUILDKIT=1 docker build --squash --no-cache --progress=plain --secret id=dctm-secrets,src=./dctm-secrets \
--build-arg INSTALL_OWNER=${INSTALL_OWNER}                                                       \
--build-arg INSTALL_BASE=${INSTALL_BASE}                                                         \
--build-arg INSTALL_HOME=${INSTALL_HOME}                                                         \
--build-arg INSTALL_TMP=${INSTALL_TMP}                                                           \
--build-arg INITIAL_DOCBROKER=${INITIAL_DOCBROKER}                                               \
--build-arg INITIAL_DOCBROKER_PORT=${INITIAL_DOCBROKER_PORT}                                     \
--build-arg DOCBASE_ADMIN=${DOCBASE_ADMIN}                                                       \
--build-arg DOCUMENTUM=${DOCUMENTUM}                                                             \
--build-arg DOCUMENTUM_OWNER=${DOCUMENTUM_OWNER}                                                 \
--build-arg GLOBAL_REGISTRY=${GLOBAL_REGISTRY}                                                   \
--build-arg JAVA_HOME=${JAVA_HOME}                                                               \
--build-arg TOMCAT_VERSION=${TOMCAT_VERSION}                                                     \
--build-arg CATALINA_BASE=${CATALINA_BASE}                                                       \
--build-arg CATALINA_HOME=${CATALINA_HOME}                                                       \
--build-arg CATALINA_TMPDIR=${CATALINA_TMPDIR}                                                   \
--build-arg JRE_HOME=${JRE_HOME}                                                                 \
--build-arg CLASSPATH=${CLASSPATH}                                                               \
--tag="cec/dctm-clients-da:v1.0" --build-arg soft_repo=./files                                   \
.

The build takes up to 5 mn, with 40s spent in the ––squash option alone, so in case you modify and enhance the dockerfile, you may want to activate this option only at the end, once the dockerfile is fully debugged in order to speed up the intermediate build cycles.
Let’s see the image size:

[email protected]:~/builds/documentum/da$ docker image
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 72e7e3a93146 10 seconds ago 948MB
cec/dctm-clients-da v1.0 3f7caa1979cf About a minute ago 1.38GB
cec/dctm-clients v1.0 e8f7a22479a6 About an hour ago 625MB

We notice a 30% size increase after DA is installed in container-clients, with more than 300 Mb of additional size. The ––squash option has removed about 400 Mb of overlaid filesystem space, from 1380 Mb down to 948 Mb. The toolbox with the Documentum command-line clients plus the Documentum Administrator weighs now 948 Mb, quite the toolbox !

Running the image

Launching the image, to get either a temporary or a permanent container, is possible, see lines 145 respectively 148 in the dockerfile. When fired up, the container starts tomcat and deploys all its applications. For our tests, the container is plugged into a docker overlay network to be able to access repositories running in other containers and DA is accessed using the host’s hostname on port 8080, e.g. http://192.168.56.10:8080/da. If several such containers run on the same host, just publish their internal port 8080 to different host’s ports to avoid any conflict.
Once running, the familiar login screen is displayed. At this point, the only accessible repository presented in the drop-down control is the global registry, which is generally not very useful. To add new repositories, we could enter the container and edit ${CATALINA_BASE}/webapps/da/WEB-INF/classes/dfc.properties but we would then fall into the same limitation as with the command-line tools and their ${DOCUMENTUM}/config.dfc.properties file, which we solved using dctm_wrapper and its symlinks widql, wiapi, etc… (see the article Connecting to a Repository via a Dynamically Edited dfc.properties File (part I)). Can we do the same here with DA ? We can of course, else we wouldn’t have asked the rethoritical question 😉 . Please, see the next three paragraphs.

Using the command-lines tools in the DA container

As said before, the DA image is built upon the command-line tools’ image. Therefore, it includes them and they are still available in a DA container. The difference with A Small Footprint Docker Container with Documentum command-line Tools is that the default container’s user is now tomcat instead of dmadmin. Hence, the docker exec commands must specify the user that owns the tools, as shown below:

[email protected]:~/builds/documentum/clients$ docker exec -it --user dmadmin container-clients-da bash -l widql dmtestgr02:containergr02:1489 -Udmadmin -Pdmadmin
 
 
OpenText Documentum idql - Interactive document query interface
Copyright (c) 2018. OpenText Corporation
All rights reserved.
Client Library Release 16.4.0070.0035
 
 
Connecting to Server using docbase dmtest02
[DM_SESSION_I_SESSION_START]info: "Session 0100c3508000351c started for user dmadmin."
 
 
Connected to OpenText Documentum Server running Release 16.4.0080.0129 Linux64.Oracle
1> quit
Bye

The rest of the linked article still applies here.

Using the containerized DA

In order to access repositories, their docbroker must be added to the DA’s ${CATALINA_BASE}/webapps/da/WEB-INF/classes/dfc.properties file. Besides manually editing this file, which is always tedious, it is possible to use a smarter trick: connect to the repository of interest through the container’s w% tools such as widql (see the aforementioned article) specifying that the new docbroker should be appended and kept in dfc.properties’ file on exiting. since this file is by default read every 30 seconds, the new configuration will be available after this delay at most. The drop down list of reachable repositories on the login screen will then display all the repositories that project to that new docbroker along with the existing docbrokers’ ones.
Example:

# first, display dmadmin's current dfc.properties file in container-client-da:
[[email protected] ~]$ cat /app/dctm/config/dfc.properties
dfc.data.dir=/app/dctm
dfc.tokenstorage.dir=/app/dctm/apptoken
dfc.tokenstorage.enable=false
dfc.docbroker.host[0]=dummy
dfc.docbroker.port[0]=1489

The couple of host/port at index 0 is assigned dummy values so values at same index position in DA own’s dfc.properties file are not hidden (they are used for the global registry).
Let’s now connect using the container’s widql tools as dmadmin from its host (as an example; network permitting, this could be done from everywhere):

[email protected]:~/builds/documentum/da$ widqlc dmtestgr02:containergr02:1489 --keep --append
...
Connected to OpenText Documentum Server running Release 16.4.0080.0129 Linux64.Oracle
1> quit

Now, display again dmadmin’s current dfc.properties file in container:

[[email protected] ~]$ cat /app/dctm/config/dfc.properties
dfc.data.dir=/app/dctm
dfc.tokenstorage.dir=/app/dctm/apptoken
dfc.tokenstorage.enable=false
dfc.docbroker.host[0]=dummy
dfc.docbroker.port[0]=1489
dfc.docbroker.host[1]=containergr02
dfc.docbroker.port[1]=1489

The new entries have been added at index position 1.
Log off/on DA and the repository dmtestgr02 should be listed in the Repository drop-down control.
Note that the widqlc command used is an alias for:

[email protected]:~/builds/documentum/da$ alias widqlc
alias widqlc='docker exec -it --user dmadmin dctm-clients-da bash -l widql'

as explained in previous paragraph.
But is there still a better, clearer way instead of switching tools ? Yes, indeed. It is the purpose of the custom docbrokers application that is deployed along DA. Read on.

The docbrokers application

What about calling up a web page to show or edit DA’s dfc.properties file ? E.g.:

http://da-container:8080/docbrokers/show
http://da-container:8080/docbrokers/append?dmtest02:container02:1489
http://da-container:8080/docbrokers/remove?index=n

These are the functions implemented by the docbrokers application:
show just displays in a web page the current content of the DA’s dfc.properties file;
append adds a couple of dfc.docbroker.host/dfc.docbroker.port to DA’s dfc.properties file using the next available index;
remove deletes the entry couple at the given index;
The default page, index, is loaded when no action is specified in the URL, e.g. http://da-container:8080/docbrokers. It simply displays a list of the supported actions. Here is its content:

#! /bin/bash
# cec - dbi-services - September 2019
 
# the index.html page;
# Usage:
#        http://...:.../docbrokers
# it displays the available functions in a html page;
 
cat <<eot
Content-type: text/html
 
 
<html>
<head><title>docbrokers application's available functions</title></head>
<body bgcolor=#F3E2A9 text=black>
<b>To show the current content of the $CATALINA_BASE/webapps/da/WEB-INF/classes/dfc.properties: </b><button type="button" autofocus onclick="window.open('http://${HTTP_HOST}/docbrokers/show', '_blank');">Show</button>
<br>
Use: http://${HTTP_HOST}/docbrokers/show
<br>
<br>
<b>To append an entry in the dfc.properties file: </b><button type="button" autofocus onclick="window.open('http://${HTTP_HOST}/docbrokers/append', '_blank');">Append</button>
<br>
Use: http://${HTTP_HOST}/docbrokers/append?[repository:]machine:port
<br>
<br>
<b>To remove the index-th entry from the dfc.properties file: </b><button type="button" autofocus onclick="window.open('http://${HTTP_HOST}/docbrokers/remove?index=n', '_blank');">Remove</button>
<br>
Use: http://${HTTP_HOST}/docbrokers/remove?index=n
<br>
<br>
</body>
<button type="button" autofocus onclick="window.open('http://${HTTP_HOST}/da/component/logoff', '_blank');">To logged out screen</button>
</html>
eot

To keep things simple, append reuses the bash dctm-wrapper script, which will force us to do some CGI programming. Fortunately, Tomcat includes a, normally disabled, cgi servlet, and it will let us do that.
Here is the show function:

#! /bin/bash
# cec - dbi-services - September 2019
# the show function;
# Usage:
#        http://...:.../docbrokers/show
# it displays in a html page the content of the dfc.properties file pointed to by $DFC_CONFIG;
 
cat <<eot
Content-type: text/html
 
 
<html>
<head><title>Show dfc.properties entries</title></head>
<body bgcolor=#F3E2A9 text=black>
<b>Here is the current content of the $CATALINA_BASE/webapps/da/WEB-INF/classes/dfc.properties:</b>
<br>
<br>
$(cat $CATALINA_BASE/webapps/da/WEB-INF/classes/dfc.properties | gawk '{printf("%s\n<br>\n", $0)}')
<br>
</body>
<button type="button" autofocus onclick="window.open('http://${HTTP_HOST}/da/component/logoff', '_blank');">To logged out screen</button>
</html>
eot

Here is the append function:

#! /bin/bash
# cec - dbi-services - September 2019
# the append function;
# Usage:
#        http://...:.../docbrokers/append?repo:machine:port
# it invokes the dctm-wrapper script to add an entry into the dfc.properties file pointed to by $DFC_CONFIG;
# the above will append the entries dfc.docbroker.host[i]=machine and dfc.docbroker.port[i]=port
# where i is the next available index;
 
export DFC_CONFIG=/app/tomcat/apache-tomcat-8.5.43/webapps/da/WEB-INF/classes/dfc.properties
 
cat <<eot
Content-type: text/html
 
 
<meta http-equiv="pragma" content="no-cache" />
<html>
<head><title>Docbrokers append</title></head>
<body bgcolor=#F3E2A9 text=black>
<br>
<b>$DFC_CONFIG file before:</b>
<br>
$(cat $DFC_CONFIG | gawk '{printf("%s\n<br>\n", $0)}')
<br>
eot
 
[[ ! -z "$QUERY_STRING" ]] && chmod a+r $DFC_CONFIG; sudo su - dmadmin -c "export DFC_CONFIG=${DFC_CONFIG}; /app/scripts/dctm-wrapper "$(if [[ -z $(echo $QUERY_STRING | cut -d: -f 3) ]]; then echo dummy:$QUERY_STRING; else echo $QUERY_STRING; fi)" --append" > /tmp/$$; mv /tmp/$$ $DFC_CONFIG
 
cat <<eot
<b>$DFC_CONFIG file after:</b>
<br>
$(cat $DFC_CONFIG | gawk '{printf("%s\n<br>\n", $0)}')
<br>
 
</body>
<button type="button" autofocus onclick="window.open('http://${HTTP_HOST}/da/component/logoff', '_blank');">To logged out screen</button>
</html>
eot

Here is the remove function:

#! /bin/bash
# cec - dbi-services - September 2019
# the remove function;
# Usage:
#        http://...:.../docbrokers/remove?entry=n
# it removes the nth entries dfc.docbroker.host[n] & dfc.docbroker.port[n] from the dfc.properties file pointed to by $DFC_CONFIG;
 
export DFC_CONFIG=${CATALINA_BASE}/webapps/da/WEB-INF/classes/dfc.properties
 
cat <<eot
Content-type: text/html
 
 
<html>
<head><title>Docbrokers remove</title></head>
<body bgcolor=#F3E2A9 text=black>
 
<b>$DFC_CONFIG file before:</b>
<br>
$(cat $CATALINA_BASE/webapps/da/WEB-INF/classes/dfc.properties | gawk '{printf("%s\n<br>\n", $0)}')
<br>
eot
 
# syntax is: http://da-container:8080/docbrokers/remove?index=n
gawk -v QUERY_STRING="$QUERY_STRING" 'BEGIN {
   nb_fields = split(QUERY_STRING, tab, "=")
   if (nb_fields != 2 || tab[1] != "index" || !match(tab[2], /^[0-9]+$/))
      tab[2]="n"
   index_to_remove = tab[2]
}
{
   if (!match($0, /^dfc.docbroker.host\[[0-9]+\]=/)) print
   else {
      match($0, /\[[0-9]+\]/); index_number = substr($0, RSTART + 1, RLENGTH - 2)
      if (index_number != index_to_remove) {
         print;
         getline; print
      }
      else getline
   }
}' $DFC_CONFIG > /tmp/$$; mv /tmp/$$ $DFC_CONFIG
 
cat <<eot
<b>$DFC_CONFIG file after:</b>
<br>
$(cat $DFC_CONFIG | gawk '{printf("%s\n<br>\n", $0)}')
<br>
 
</body>
<button type="button" autofocus onclick="window.open('http://${HTTP_HOST}/da/component/logoff', '_blank');">To logged out screen</button>
</html>
eot

All these scripts belong to the new docbrokers application; they are archived in docbrokers.tar which is later exploded in the webapps directory (see line 70 in the dockerfile). All the configuration changes at the tomcat level (e.g. to activate the cgi servlet) are located in the application own’s WEB-INF/context.xml and WEB-INF/web.xml so they don’t interfere with the other deployed applications.

With these functions, it is very easy to edit the containerized DA’s dfc.properties file. After the change, a click on the button “To logged out screen” will display DA’s logoff page from which the login page is one click away. This step is necessary to make sure the Repository drop-down control is properly updated to reflect the requested change.

Conclusion

Our toolbox has now a few basic tools to help us do some of our daily administrative tasks. While quite useful as-is, the containerized DA could still be enhanced to remove its dependency from an external repository, e.g. having a local, dedicated global registry along with its local database, typically a PostgreSQL one. The image would be much bigger but also much more transportable. Time permitting, we’ll implement this alternative.
Another improvement area would be to add a clean shutdown of the tomcat server, for example using signals, as presented in the article How to stop Documentum processes in a docker container, and more (part I) and maybe add some restarting and/or monitoring facility. Also, now that the Pandora box has been opened of custom extensions deployment in tomcat along DA, anything is possible in that container.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Cesare Cervini
Cesare Cervini

Consultant