<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Shell Scripting on IAMDevBox</title><link>https://www.iamdevbox.com/tags/shell-scripting/</link><description>Recent content in Shell Scripting on IAMDevBox</description><image><title>IAMDevBox</title><url>https://www.iamdevbox.com/IAMDevBox.com.jpg</url><link>https://www.iamdevbox.com/IAMDevBox.com.jpg</link></image><generator>Hugo -- 0.146.0</generator><language>en-us</language><lastBuildDate>Thu, 20 Aug 2026 22:18:49 -0400</lastBuildDate><atom:link href="https://www.iamdevbox.com/tags/shell-scripting/index.xml" rel="self" type="application/rss+xml"/><item><title>Java, MySQL, and Shell Scripting for SailPoint IdentityIQ</title><link>https://www.iamdevbox.com/posts/sailpoint-identityiq-java-mysql-shell-scripting-guide/</link><pubDate>Thu, 20 Aug 2026 14:00:00 +0000</pubDate><guid>https://www.iamdevbox.com/posts/sailpoint-identityiq-java-mysql-shell-scripting-guide/</guid><description>SailPoint IdentityIQ Java, MySQL, and shell scripting: custom JAR deployment, the spt_ schema, safe diagnostic SQL, and iiq console automation scripts.</description><content:encoded><![CDATA[<p>SailPoint IdentityIQ is a Java web application running on an application server against a relational database. Most IdentityIQ development happens in BeanShell rules and XML workflows — covered in the companion guide to <a href="/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/">IdentityIQ BeanShell rules, workflows, and tasks</a>. This article covers the layer underneath: when to write compiled Java instead of BeanShell, how the MySQL schema is actually laid out, and the shell scripting that turns manual console work into repeatable automation.</p>
<h2 id="the-stack-concretely">The Stack, Concretely</h2>
<p>An IdentityIQ deployment is four layers, and knowing which one a problem lives in cuts debugging time dramatically:</p>
<table>
  <thead>
      <tr>
          <th>Layer</th>
          <th>What lives there</th>
          <th>Where to look</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Application server</td>
          <td>Tomcat/WebSphere/WebLogic, JVM heap, threads</td>
          <td><code>catalina.out</code>, thread dumps</td>
      </tr>
      <tr>
          <td>IdentityIQ WAR</td>
          <td>Your JARs, rules, config</td>
          <td><code>IdentityIQ_HOME/WEB-INF/</code></td>
      </tr>
      <tr>
          <td>Database</td>
          <td>All objects, most as XML blobs</td>
          <td><code>spt_</code> tables</td>
      </tr>
      <tr>
          <td>Target systems</td>
          <td>AD, LDAP, HR feeds, apps</td>
          <td>Connector logs</td>
      </tr>
  </tbody>
</table>
<p><code>IdentityIQ_HOME</code> is wherever <code>identityiq.war</code> was expanded — typically <code>$TOMCAT_HOME/webapps/identityiq</code>. Nearly every path in this article is relative to it.</p>
<h2 id="java-when-to-leave-beanshell">Java: When to Leave BeanShell</h2>
<p>BeanShell is convenient for short scripts, but it is interpreted, untyped, and untestable. Move to compiled Java when any of these apply:</p>
<ul>
<li>The logic exceeds roughly 50 lines</li>
<li>You need unit tests</li>
<li>It runs in a hot path — per-account during aggregation, for instance</li>
<li>You need a library BeanShell struggles to use cleanly</li>
<li>You are writing a custom connector or task executor</li>
</ul>
<h3 id="compiling-against-the-identityiq-api">Compiling Against the IdentityIQ API</h3>
<p>Your code compiles against <code>identityiq.jar</code>, found in <code>IdentityIQ_HOME/WEB-INF/lib/</code>. A minimal Maven setup installs it into your local repository, since SailPoint does not publish to Maven Central:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mvn install:install-file <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -Dfile<span style="color:#f92672">=</span>/opt/tomcat/webapps/identityiq/WEB-INF/lib/identityiq.jar <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -DgroupId<span style="color:#f92672">=</span>sailpoint <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -DartifactId<span style="color:#f92672">=</span>identityiq <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -Dversion<span style="color:#f92672">=</span>8.4 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  -Dpackaging<span style="color:#f92672">=</span>jar
</span></span></code></pre></div><p>Then declare it as <code>provided</code> — it must not be bundled into your artifact, because the container already has it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#f92672">&lt;dependency&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;groupId&gt;</span>sailpoint<span style="color:#f92672">&lt;/groupId&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;artifactId&gt;</span>identityiq<span style="color:#f92672">&lt;/artifactId&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;version&gt;</span>8.4<span style="color:#f92672">&lt;/version&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;scope&gt;</span>provided<span style="color:#f92672">&lt;/scope&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/dependency&gt;</span>
</span></span></code></pre></div><p>Marking it <code>compile</code> instead ships a second copy of every SailPoint class inside your JAR, producing <code>ClassCastException</code> errors where the same class loaded by two classloaders is not considered equal. This is one of the harder IdentityIQ bugs to diagnose, because the exception message names the same class on both sides.</p>
<h3 id="java-version-compatibility">Java Version Compatibility</h3>
<p>IdentityIQ 8.x supports Java 8 and 11, with 8.4 adding Java 17 on supported application servers. Compile targeting the version your application server actually runs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>java -version                       <span style="color:#75715e"># on the app server host</span>
</span></span><span style="display:flex;"><span>mvn -DskipTests package             <span style="color:#75715e"># with maven.compiler.release matching</span>
</span></span></code></pre></div><p>A mismatch produces <code>UnsupportedClassVersionError</code> at class load time — not at deployment — so the failure shows up the first time your code is invoked, often long after the deploy appeared to succeed.</p>
<h3 id="deploying-a-custom-jar">Deploying a Custom JAR</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl stop tomcat
</span></span><span style="display:flex;"><span>sudo cp target/iiq-custom-1.0.0.jar /opt/tomcat/webapps/identityiq/WEB-INF/lib/
</span></span><span style="display:flex;"><span>sudo chown tomcat:tomcat /opt/tomcat/webapps/identityiq/WEB-INF/lib/iiq-custom-1.0.0.jar
</span></span><span style="display:flex;"><span>sudo systemctl start tomcat
</span></span></code></pre></div><p>A restart is mandatory — the JVM does not reload classes from <code>WEB-INF/lib</code> at runtime. This is the key operational difference from rules, which are database objects you can update live.</p>
<p><strong>Remove the old version explicitly</strong> when deploying an update with a changed filename. Two JARs both containing <code>com.example.iiq.MyRule</code> produce nondeterministic behaviour depending on classloader ordering.</p>
<h2 id="mysql-the-identityiq-schema">MySQL: The IdentityIQ Schema</h2>
<h3 id="generating-and-loading-the-schema">Generating and Loading the Schema</h3>
<p>IdentityIQ generates its own DDL. From <code>IdentityIQ_HOME/WEB-INF/bin</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./iiq schema
</span></span></code></pre></div><p>This writes versioned scripts into <code>IdentityIQ_HOME/WEB-INF/database</code>, named like <code>create_identityiq_tables-8.4.mysql</code>. Load the one matching your database platform:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mysql -u root -p identityiq &lt; create_identityiq_tables-8.4.mysql
</span></span></code></pre></div><p>Expect this to take anywhere from 45 minutes to 2 hours. Re-run <code>./iiq schema</code> after adding extended attributes — they become real columns, and the generated DDL changes.</p>
<h3 id="connection-settings">Connection Settings</h3>
<p>Connection configuration lives in <code>IdentityIQ_HOME/WEB-INF/classes/iiq.properties</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-properties" data-lang="properties"><span style="display:flex;"><span><span style="color:#a6e22e">dataSource.url</span><span style="color:#f92672">=</span><span style="color:#e6db74">jdbc:mysql://db.example.com:3306/identityiq?useUnicode=true&amp;characterEncoding=utf8</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dataSource.username</span><span style="color:#f92672">=</span><span style="color:#e6db74">identityiq</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dataSource.password</span><span style="color:#f92672">=</span><span style="color:#e6db74">&lt;encrypted-value&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dataSource.maxActive</span><span style="color:#f92672">=</span><span style="color:#e6db74">50</span>
</span></span></code></pre></div><p>The password must be encrypted. Generate the ciphertext with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./iiq encrypt changeit
</span></span></code></pre></div><p>Paste the output into <code>iiq.properties</code>. IdentityIQ will not accept a plaintext password here.</p>
<h3 id="the-tables-you-will-actually-query">The Tables You Will Actually Query</h3>
<p>Every table carries the <code>spt_</code> prefix. These are the ones worth knowing:</p>
<table>
  <thead>
      <tr>
          <th>Table</th>
          <th>Contents</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>spt_identity</code></td>
          <td>The identity cube — one row per person</td>
      </tr>
      <tr>
          <td><code>spt_link</code></td>
          <td>Accounts on target systems, linked to identities</td>
      </tr>
      <tr>
          <td><code>spt_application</code></td>
          <td>Connector configurations</td>
      </tr>
      <tr>
          <td><code>spt_bundle</code></td>
          <td>Roles</td>
      </tr>
      <tr>
          <td><code>spt_identity_entitlement</code></td>
          <td>Who currently holds which entitlement</td>
      </tr>
      <tr>
          <td><code>spt_task_result</code></td>
          <td>Task execution history and results</td>
      </tr>
      <tr>
          <td><code>spt_workflow_case</code></td>
          <td>In-flight workflow state</td>
      </tr>
      <tr>
          <td><code>spt_work_item</code></td>
          <td>Pending approvals and manual actions</td>
      </tr>
      <tr>
          <td><code>spt_syslog_event</code></td>
          <td>System errors and warnings</td>
      </tr>
      <tr>
          <td><code>spt_audit_event</code></td>
          <td>Audit trail</td>
      </tr>
  </tbody>
</table>
<h3 id="read-only-diagnostics-that-save-real-time">Read-Only Diagnostics That Save Real Time</h3>
<p>These queries answer questions the UI makes tedious. All are <code>SELECT</code> only.</p>
<p><strong>Which aggregations are failing, and how recently:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> name,
</span></span><span style="display:flex;"><span>       FROM_UNIXTIME(completed<span style="color:#f92672">/</span><span style="color:#ae81ff">1000</span>) <span style="color:#66d9ef">AS</span> completed_at,
</span></span><span style="display:flex;"><span>       completion_status,
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">SUBSTRING</span>(messages, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">200</span>)   <span style="color:#66d9ef">AS</span> first_message
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span>   spt_task_result
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span>  completion_status <span style="color:#66d9ef">IN</span> (<span style="color:#e6db74">&#39;Error&#39;</span>, <span style="color:#e6db74">&#39;Warning&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ORDER</span>  <span style="color:#66d9ef">BY</span> completed <span style="color:#66d9ef">DESC</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">LIMIT</span>  <span style="color:#ae81ff">20</span>;
</span></span></code></pre></div><p><strong>Accounts that failed to correlate</strong> — the usual cause of &ldquo;the user exists but has no access&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> a.name        <span style="color:#66d9ef">AS</span> application,
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">COUNT</span>(<span style="color:#f92672">*</span>)      <span style="color:#66d9ef">AS</span> uncorrelated_accounts
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span>   spt_link l
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">JOIN</span>   spt_application a <span style="color:#66d9ef">ON</span> l.application <span style="color:#f92672">=</span> a.id
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span>  l.identity_id <span style="color:#66d9ef">IS</span> <span style="color:#66d9ef">NULL</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">GROUP</span>  <span style="color:#66d9ef">BY</span> a.name
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ORDER</span>  <span style="color:#66d9ef">BY</span> uncorrelated_accounts <span style="color:#66d9ef">DESC</span>;
</span></span></code></pre></div><p><strong>Workflows stuck in flight</strong>, which accumulate invisibly and eventually degrade performance:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> name,
</span></span><span style="display:flex;"><span>       FROM_UNIXTIME(created<span style="color:#f92672">/</span><span style="color:#ae81ff">1000</span>) <span style="color:#66d9ef">AS</span> created_at,
</span></span><span style="display:flex;"><span>       DATEDIFF(NOW(), FROM_UNIXTIME(created<span style="color:#f92672">/</span><span style="color:#ae81ff">1000</span>)) <span style="color:#66d9ef">AS</span> age_days
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span>   spt_workflow_case
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span>  DATEDIFF(NOW(), FROM_UNIXTIME(created<span style="color:#f92672">/</span><span style="color:#ae81ff">1000</span>)) <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">30</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ORDER</span>  <span style="color:#66d9ef">BY</span> created <span style="color:#66d9ef">ASC</span>;
</span></span></code></pre></div><p><strong>Table sizes</strong>, to find what is actually consuming disk:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#66d9ef">table_name</span>,
</span></span><span style="display:flex;"><span>       ROUND(((data_length <span style="color:#f92672">+</span> index_length) <span style="color:#f92672">/</span> <span style="color:#ae81ff">1024</span> <span style="color:#f92672">/</span> <span style="color:#ae81ff">1024</span>), <span style="color:#ae81ff">1</span>) <span style="color:#66d9ef">AS</span> size_mb,
</span></span><span style="display:flex;"><span>       table_rows
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span>   information_schema.TABLES
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span>  table_schema <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;identityiq&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">ORDER</span>  <span style="color:#66d9ef">BY</span> (data_length <span style="color:#f92672">+</span> index_length) <span style="color:#66d9ef">DESC</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">LIMIT</span>  <span style="color:#ae81ff">15</span>;
</span></span></code></pre></div><p>Note that IdentityIQ stores timestamps as Unix epoch <strong>milliseconds</strong> in <code>BIGINT</code> columns, which is why every date needs <code>FROM_UNIXTIME(col/1000)</code>.</p>
<h3 id="never-write-directly-to-the-database">Never Write Directly to the Database</h3>
<p>This deserves emphasis because it is the most damaging mistake available to someone comfortable with SQL.</p>
<p>Most IdentityIQ objects serialize their real content into an <strong>XML blob column</strong>. The relational columns beside it are a partial, denormalized projection maintained by the application for querying. An <code>UPDATE</code> that changes a column leaves the XML blob untouched, so the object now disagrees with itself — and the XML wins the next time the object loads.</p>
<p>Compounding this, Hibernate caches objects in memory. A direct SQL change to a cached object is silently overwritten the next time the application saves it.</p>
<p>Use the iiq console, the API, or a task. <code>SELECT</code> freely; never <code>UPDATE</code>, <code>INSERT</code>, or <code>DELETE</code>.</p>
<h3 id="keeping-the-database-from-growing-forever">Keeping the Database from Growing Forever</h3>
<p>Four tables grow without bound if left alone: <code>spt_task_result</code>, <code>spt_syslog_event</code>, <code>spt_audit_event</code>, and <code>spt_provisioning_transaction</code>. On a busy deployment <code>spt_syslog_event</code> can reach tens of millions of rows, at which point ordinary queries slow noticeably.</p>
<p>Schedule the built-in <strong>Perform Maintenance</strong> task and configure retention in System Setup. Verify it is working:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#66d9ef">COUNT</span>(<span style="color:#f92672">*</span>)                          <span style="color:#66d9ef">AS</span> total,
</span></span><span style="display:flex;"><span>       FROM_UNIXTIME(<span style="color:#66d9ef">MIN</span>(created)<span style="color:#f92672">/</span><span style="color:#ae81ff">1000</span>)  <span style="color:#66d9ef">AS</span> oldest,
</span></span><span style="display:flex;"><span>       FROM_UNIXTIME(<span style="color:#66d9ef">MAX</span>(created)<span style="color:#f92672">/</span><span style="color:#ae81ff">1000</span>)  <span style="color:#66d9ef">AS</span> newest
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span>   spt_syslog_event;
</span></span></code></pre></div><p>If <code>oldest</code> predates your retention window, purging is not running.</p>
<h3 id="mysql-settings-that-matter">MySQL Settings That Matter</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[mysqld]</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">innodb_buffer_pool_size</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">8G        # size to available RAM</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">max_allowed_packet</span>      <span style="color:#f92672">=</span> <span style="color:#e6db74">64M       # large XML blobs exceed the 4M default</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">innodb_log_file_size</span>    <span style="color:#f92672">=</span> <span style="color:#e6db74">512M</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">character_set_server</span>    <span style="color:#f92672">=</span> <span style="color:#e6db74">utf8mb4</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">transaction_isolation</span>   <span style="color:#f92672">=</span> <span style="color:#e6db74">READ-COMMITTED</span>
</span></span></code></pre></div><p><code>max_allowed_packet</code> is the one that bites first. IdentityIQ writes large XML blobs, and the default rejects them with <code>Packet for query is too large</code>, usually during aggregation of a large application.</p>
<h2 id="shell-scripting-automating-the-console">Shell Scripting: Automating the Console</h2>
<p>The <code>iiq console</code> reads from stdin, which makes it scriptable. This is the foundation for backup, deployment, and health-check automation.</p>
<h3 id="exporting-objects-for-version-control">Exporting Objects for Version Control</h3>
<p>IdentityIQ objects live in the database and are therefore invisible to Git. A database refresh destroys uncommitted customization. This script exports them:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>set -euo pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>IIQ_HOME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>IIQ_HOME<span style="color:#66d9ef">:-</span>/opt/tomcat/webapps/identityiq<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>IIQ_BIN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$IIQ_HOME<span style="color:#e6db74">/WEB-INF/bin&#34;</span>
</span></span><span style="display:flex;"><span>EXPORT_DIR<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>1<span style="color:#66d9ef">:-</span>./iiq-export<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mkdir -p <span style="color:#e6db74">&#34;</span>$EXPORT_DIR<span style="color:#e6db74">&#34;</span>/<span style="color:#f92672">{</span>rules,workflows,tasks,applications<span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>export_class<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  local cls<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> dest<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$2<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;Exporting </span>$cls<span style="color:#e6db74">...&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;</span>$IIQ_BIN<span style="color:#e6db74">/iiq&#34;</span> console <span style="color:#e6db74">&lt;&lt;CONSOLE | grep -v &#39;^&gt;&#39; &gt; &#34;$dest/_list.txt&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">list $cls
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">CONSOLE</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">while</span> IFS<span style="color:#f92672">=</span> read -r name; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">[[</span> -z <span style="color:#e6db74">&#34;</span>$name<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>    local safe<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>name//[^a-zA-Z0-9._-]/_<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;</span>$IIQ_BIN<span style="color:#e6db74">/iiq&#34;</span> console <span style="color:#e6db74">&lt;&lt;CONSOLE &gt;/dev/null
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">checkout $cls &#34;$name&#34; $dest/$safe.xml
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">CONSOLE</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">done</span> &lt; <span style="color:#e6db74">&#34;</span>$dest<span style="color:#e6db74">/_list.txt&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>export_class Rule         <span style="color:#e6db74">&#34;</span>$EXPORT_DIR<span style="color:#e6db74">/rules&#34;</span>
</span></span><span style="display:flex;"><span>export_class Workflow     <span style="color:#e6db74">&#34;</span>$EXPORT_DIR<span style="color:#e6db74">/workflows&#34;</span>
</span></span><span style="display:flex;"><span>export_class TaskDefinition <span style="color:#e6db74">&#34;</span>$EXPORT_DIR<span style="color:#e6db74">/tasks&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Exported to </span>$EXPORT_DIR<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>Run it on a schedule, commit the output, and a database refresh becomes recoverable.</p>
<h3 id="deploying-xml-with-validation">Deploying XML with Validation</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>set -euo pipefail
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>IIQ_BIN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>IIQ_HOME<span style="color:#66d9ef">:-</span>/opt/tomcat/webapps/identityiq<span style="color:#e6db74">}</span><span style="color:#e6db74">/WEB-INF/bin&#34;</span>
</span></span><span style="display:flex;"><span>TARGET<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> ! -e <span style="color:#e6db74">&#34;</span>$TARGET<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;ERROR: </span>$TARGET<span style="color:#e6db74"> not found&#34;</span> &gt;&amp;<span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>  exit <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Fail before touching IdentityIQ if the XML is malformed</span>
</span></span><span style="display:flex;"><span>find <span style="color:#e6db74">&#34;</span>$TARGET<span style="color:#e6db74">&#34;</span> -name <span style="color:#e6db74">&#39;*.xml&#39;</span> -print0 | <span style="color:#66d9ef">while</span> IFS<span style="color:#f92672">=</span> read -r -d <span style="color:#e6db74">&#39;&#39;</span> f; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  xmllint --noout <span style="color:#e6db74">&#34;</span>$f<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">||</span> <span style="color:#f92672">{</span> echo <span style="color:#e6db74">&#34;ERROR: invalid XML in </span>$f<span style="color:#e6db74">&#34;</span> &gt;&amp;2; exit 1; <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> f in <span style="color:#66d9ef">$(</span>find <span style="color:#e6db74">&#34;</span>$TARGET<span style="color:#e6db74">&#34;</span> -name <span style="color:#e6db74">&#39;*.xml&#39;</span> | sort<span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;Importing </span><span style="color:#66d9ef">$(</span>basename <span style="color:#e6db74">&#34;</span>$f<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;</span>$IIQ_BIN<span style="color:#e6db74">/iiq&#34;</span> console <span style="color:#e6db74">&lt;&lt;CONSOLE
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">import $f
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">CONSOLE</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>The <code>xmllint</code> pre-check matters because <code>import</code> on malformed XML can partially apply, leaving the environment in a state neither matching the old nor the new definition.</p>
<h3 id="a-health-check-worth-cron-ing">A Health Check Worth Cron-ing</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#75715e"># Report IdentityIQ health; exit non-zero if anything is wrong.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>DB_USER<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>DB_USER<span style="color:#66d9ef">:-</span>identityiq<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>DB_NAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>DB_NAME<span style="color:#66d9ef">:-</span>identityiq<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>CATALINA_OUT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>CATALINA_OUT<span style="color:#66d9ef">:-</span>/opt/tomcat/logs/catalina.out<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>status<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>q<span style="color:#f92672">()</span> <span style="color:#f92672">{</span> mysql -u <span style="color:#e6db74">&#34;</span>$DB_USER<span style="color:#e6db74">&#34;</span> -p<span style="color:#e6db74">&#34;</span>$DB_PASS<span style="color:#e6db74">&#34;</span> -N -B -e <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;</span>$DB_NAME<span style="color:#e6db74">&#34;</span> 2&gt;/dev/null; <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>failed<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>q <span style="color:#e6db74">&#34;SELECT COUNT(*) FROM spt_task_result
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            WHERE completion_status=&#39;Error&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">              AND completed &gt; (UNIX_TIMESTAMP(NOW() - INTERVAL 1 DAY) * 1000);&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>failed<span style="color:#66d9ef">:-</span>0<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> -gt <span style="color:#ae81ff">0</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;WARN: </span>$failed<span style="color:#e6db74"> task(s) failed in the last 24h&#34;</span>
</span></span><span style="display:flex;"><span>  status<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>stuck<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>q <span style="color:#e6db74">&#34;SELECT COUNT(*) FROM spt_workflow_case
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">           WHERE created &lt; (UNIX_TIMESTAMP(NOW() - INTERVAL 30 DAY) * 1000);&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>stuck<span style="color:#66d9ef">:-</span>0<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> -gt <span style="color:#ae81ff">0</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;WARN: </span>$stuck<span style="color:#e6db74"> workflow case(s) older than 30 days&#34;</span>
</span></span><span style="display:flex;"><span>  status<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> -f <span style="color:#e6db74">&#34;</span>$CATALINA_OUT<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>  oom<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>grep -c <span style="color:#e6db74">&#39;OutOfMemoryError&#39;</span> <span style="color:#e6db74">&#34;</span>$CATALINA_OUT<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">||</span> true<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">[[</span> <span style="color:#e6db74">&#34;</span>$oom<span style="color:#e6db74">&#34;</span> -gt <span style="color:#ae81ff">0</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">{</span> echo <span style="color:#e6db74">&#34;CRITICAL: </span>$oom<span style="color:#e6db74"> OutOfMemoryError in catalina.out&#34;</span>; status<span style="color:#f92672">=</span>2; <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>exit $status
</span></span></code></pre></div><p>Note <code>-p&quot;$DB_PASS&quot;</code> reads from the environment rather than hardcoding a credential, and <code>-N -B</code> strips headers and formatting so the output parses cleanly.</p>
<h3 id="watching-aggregation-in-real-time">Watching Aggregation in Real Time</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Follow only connector activity during an aggregation run</span>
</span></span><span style="display:flex;"><span>tail -f /opt/tomcat/logs/catalina.out | grep --line-buffered -E <span style="color:#e6db74">&#39;sailpoint.connector|Aggregation&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Count errors by type from today&#39;s log</span>
</span></span><span style="display:flex;"><span>grep <span style="color:#e6db74">&#39;ERROR&#39;</span> /opt/tomcat/logs/catalina.out <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  | sed -E <span style="color:#e6db74">&#39;s/.*ERROR[[:space:]]+//&#39;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  | cut -d<span style="color:#e6db74">&#39; &#39;</span> -f1 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  | sort | uniq -c | sort -rn | head -20
</span></span></code></pre></div><p><code>--line-buffered</code> on <code>grep</code> is what makes the first command actually stream; without it grep buffers output and the tail appears frozen.</p>
<h2 id="putting-it-together">Putting It Together</h2>
<p>A sound IdentityIQ deployment pipeline uses all three layers: shell scripts export objects from development into Git, compiled Java holds logic too complex for BeanShell, and MySQL is queried read-only for diagnostics while all writes go through the IdentityIQ API.</p>
<p>The governance logic you build on top of this — rules, workflows, and tasks — is covered in the companion guide to <a href="/posts/sailpoint-identityiq-beanshell-rules-workflows-tasks-developer-guide/">IdentityIQ BeanShell rules, workflows, and tasks</a>. For how IdentityIQ compares to other platforms in this space, see our <a href="/posts/iam-tools-comparison-complete-guide-to-identity-platforms/">IAM tools comparison</a>.</p>
]]></content:encoded></item></channel></rss>