<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://rotty3000.doublebite.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://rotty3000.doublebite.com/" rel="alternate" type="text/html" /><updated>2025-06-26T03:31:57+00:00</updated><id>https://rotty3000.doublebite.com/feed.xml</id><title type="html">rotty3000.doublebite.com</title><subtitle>My personal thoughts.</subtitle><author><name>Raymond Augé</name></author><entry><title type="html">Announcing KSGate - Waste Not, Want Not!</title><link href="https://rotty3000.doublebite.com/Announcing-KSGate-Waste-Not-Want-Not/" rel="alternate" type="text/html" title="Announcing KSGate - Waste Not, Want Not!" /><published>2025-06-26T00:00:00+00:00</published><updated>2025-06-26T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/Announcing-KSGate-Waste-Not-Want-Not</id><content type="html" xml:base="https://rotty3000.doublebite.com/Announcing-KSGate-Waste-Not-Want-Not/"><![CDATA[<blockquote>
  <h4 id="waste-not-want-not">waste not, want not</h4>
  <p><code class="language-plaintext highlighter-rouge">PROVERB</code><br />
if you use a commodity or resource carefully and without extravagance you will never be in need.</p>
</blockquote>

<p>Recently, there’s a ton of energy being spent in the Kubernetes ecosystem thinking about how to save costs.
<a href="https://github.com/ksgate/ksgate">KSGate</a> is a prototype I’ve been working on to provide a generalized and declarative solution to scheduling.
One big side effect of strict scheduling is the reduction of costly resources by avoiding two aspects of 
Kubernetes that really tend to annoy me; wasteful scheduling and weak service dependencies.</p>

<p>Let me start by talking about these two things and then I’ll explain how my experiment (<a href="https://github.com/ksgate/ksgate">KSGate</a>) plays a role in addressing them.</p>

<p>We’ll start with…</p>

<h3 id="wasteful-scheduling">Wasteful Scheduling</h3>

<p>Consider the following. Imagine we have a database and a secret the database depends on.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">database</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">database</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">postgres</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
<span class="nn">---</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Secret</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">test</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">POSTGRES_PASSWORD</span><span class="pi">:</span> <span class="s">Zm9v</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">Opaque</span>
</code></pre></div></div>

<p>If you deploy the database but the secret does not exist the pod will be scheduled and the kubelet will try to get the secret. If the secret is not ready, the kubelet will mark the pod status as <code class="language-plaintext highlighter-rouge">CreateContainerConfigError</code> and try over and over to see if the secret ever does show up. So the resources for the pod are allocated on the node but the kubelet of the node can’t actually run it. The kubelet is spending time and resources trying to get the secret none of which is useful to your application. And who knows when the secret will be ready?</p>

<p>This is just one example of wasteful scheduling but I hope you get the idea that wasteful scheduling is workload scheduling that doesn’t provide any application benefit yet still incurs infrastructure costs in an uncontrolled fashion. Do you even measure how much cost is spent on compute that is not application related?</p>

<p>Let’s move on to…</p>

<h3 id="weak-dependencies">Weak Dependencies</h3>

<p>Now what if one resource depends on another resource and the other resource is not ready?</p>

<p>Consider the following. Here we have an application that depends on a database.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">env</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_HOST</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">database</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_PORT</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">5432"</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
</code></pre></div></div>

<p>How might we model this in Kubernetes?</p>

<p>The first suggestion that you’re likely to come across is to use an init container (or maybe a sidecar container) to wait for the database to be ready. Something like the following:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">initContainers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">wait-for-database</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">busybox</span>
    <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">sh"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">-c"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">until</span><span class="nv"> </span><span class="s">nc</span><span class="nv"> </span><span class="s">-z</span><span class="nv"> </span><span class="s">database</span><span class="nv"> </span><span class="s">5432;</span><span class="nv"> </span><span class="s">do</span><span class="nv"> </span><span class="s">echo</span><span class="nv"> </span><span class="s">waiting</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">database;</span><span class="nv"> </span><span class="s">sleep</span><span class="nv"> </span><span class="s">1;</span><span class="nv"> </span><span class="s">done"</span><span class="pi">]</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">env</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_HOST</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">database</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_PORT</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">5432"</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
</code></pre></div></div>

<p>While functional this approach is inefficient and wasteful because the application pod is scheduled and consuming resources even though it can’t actually do something meaningful until the database is ready and there is no way to express this dependency in a way that the scheduler will respect it.</p>

<p>That’s why I refer to this as a <em>weak dependency</em>.</p>

<p>Either way…</p>

<h3 id="why-should-we-care">Why should we care?</h3>

<p>The two cases above have to be accepted as known wastes of resources. We know it will happen, but we can’t really do anything about it. However, factored across the entire architecture this could amount to a significant cost.</p>

<p>Until recently, however, there were no better options. The scheduler is allocating resources for essentially useless processes that are consuming resources and executing logic which is not part of our application.</p>

<p>This means that we’re not using our resources efficiently and as cloud resources increase in price this wastefulness saps the value from our infrastructure.</p>

<p>Enter…</p>

<h3 id="scheduling-gates">Scheduling Gates</h3>

<p>Luckily the Kubernetes maintainers have thought about this and have put hooks in place around the scheduler which allow us to control the scheduling of workloads until some set of conditions are met. This mechanism is called <em>scheduling gates</em> or more precisely <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/pod-scheduling-readiness/">Pod Scheduling Readiness</a>. It reached <code class="language-plaintext highlighter-rouge">stable</code> status in Kubernetes 1.30.</p>

<p>Today, we can create a scheduling gate for the database in the first scenario that indicates the need for the secret and then <em>wait for the gate to be removed</em> before the database will be scheduled with zero application resources wasted.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">database</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">database</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">postgres</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
  <span class="na">schedulingGates</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my.scheduling.gate/db-secret</span>
</code></pre></div></div>

<p>Since the scheduler won’t schedule the database pod until the gate is removed no resources are wasted. There is no logic executing, no resources are even allocated let alone consumed, and this state can be maintained indefinitely at essentially zero cost.</p>

<p>Similarly, we can create a scheduling gate for the application that indicates the need for the database and again <em>wait for the gate to be removed</em> before the application will be scheduled.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">env</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_HOST</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">database</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_PORT</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">5432"</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
  <span class="na">schedulingGates</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my.scheduling.gate/db-secret</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my.scheduling.gate/database</span>
</code></pre></div></div>

<h3 id="but-whos-the-gatekeeper">But Who’s the Gatekeeper?</h3>

<p>The above approach is a good start, but it has a few drawbacks. You’ll note that I emphasized the phrase “<em>wait for the gate to be removed</em>”. That harkens to the fact that no one currently recognizes the scheduling gate I used so I need to write a custom controller that recognizes and knows under what conditions to remove it. As I add more and more gates I need more controllers or more code to recognize the conditions implied by each gate. This sounds like a lot of work, maybe more work than what we had before.</p>

<h4 id="declarative-scheduling-gates">Declarative Scheduling Gates</h4>

<p>What if we could define a scheduling gate in a declarative way? What if there was a scheduling gate controller that could manage the scheduling gate for us, or even better, a scheduling gate controller that could manage the scheduling gate for us <strong>and</strong> allowed us to define the conditions under which the gate should be removed in a flexible way?</p>

<p>That sounds pretty interesting doesn’t it?</p>

<p>Let’s start by considering the constraints.</p>

<p>First thing to consider is that scheduling gates are just opaque strings with a maximum length of 63 characters and the characters allowed are limited to alphanumeric, <code class="language-plaintext highlighter-rouge">/</code>, <code class="language-plaintext highlighter-rouge">-</code>, and <code class="language-plaintext highlighter-rouge">_</code>. This means that we can’t really use them for anything other than unique identifiers.</p>

<p>On the other hand the constraints we want to declare are details that are closely tied to the definition of the workload so it shouldn’t be kept far away from it or there’s a risk of it getting out of sync. But we can’t store that information in the scheduling gate so we need some other way to store it. It would be nice if we could avoid having to create a custom storage layer or a new CRD and force everyone to have to adapt to this new mechanism. That would make a generalized solution that no-one would adopt because it doesn’t integrate into existing resource definitions (like Helm charts and such.)</p>

<p>If only there was a way to store arbitrary information in the workload definition itself that could be tied to the scheduling gate identifier without the hassle of new storage layer or new CRD.</p>

<h4 id="annotations-to-the-rescue">Annotations to the Rescue</h4>

<p>As it turns out, there is a way to do this. We could use the <code class="language-plaintext highlighter-rouge">metadata.annotations</code> on the workload to store the condition information and bind it to the scheduling gate identifier.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">my.scheduling.gate/db-secret</span><span class="pi">:</span> <span class="s2">"</span><span class="s">...condition..."</span>
    <span class="na">my.scheduling.gate/database</span><span class="pi">:</span> <span class="s2">"</span><span class="s">...condition..."</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">env</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_HOST</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">database</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_PORT</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">5432"</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
  <span class="na">schedulingGates</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my.scheduling.gate/db-secret</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">my.scheduling.gate/database</span>
</code></pre></div></div>

<p>This approach of pairing a scheduling gate with a matching annotation, along with a little bit of <em>semantic-fu</em> would allow us to declare conditions directly in the definition that a generic controller could use to determine when the workload is ready to be scheduled and do it in a way that is fully compatible with existing workload definitions.</p>

<h2 id="ksgate"><a href="https://github.com/ksgate/ksgate">KSGate</a></h2>

<p>I’ve modelled this idea and I’m working on a <a href="https://github.com/ksgate/ksgate">controller</a> that will manage the <em>scheduling gate and annotation pairs</em> for us. I’ve called this controller <strong>KSGate</strong> (<em>derived from <strong>Kubernetes Scheduling Gates</strong></em>).</p>

<p>In order to avoid conflicts with any pre-existing scheduling gates out in the wild, <strong>KSGate</strong> is designed to only consider gates that are prefixed with <code class="language-plaintext highlighter-rouge">k8s.ksgate.org/</code>. The suffix that must follow is arbitrary and is used by developers to uniquely identify managed gates on the workload (that gives 46 remaining characters to work with, but considering the scope of uniqueness is constrained to the resource itself that should leave plenty of room to work with).</p>

<h3 id="conditions">Conditions</h3>

<p>Here’s an example of a condition that defines a dependency on the existence of a secret named <code class="language-plaintext highlighter-rouge">db-secret</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"apiVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"v1"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">(optional)</span><span class="w"> </span><span class="err">defaults</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="s2">"v1"</span><span class="p">,</span><span class="w"> </span><span class="err">as</span><span class="w"> </span><span class="err">in</span><span class="w"> </span><span class="err">core/v</span><span class="mi">1</span><span class="w">
  </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Secret"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">required</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"db-secret"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">required</span><span class="w">
  </span><span class="nl">"namespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"default"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">(optional)</span><span class="w"> </span><span class="err">defaults</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">resources's</span><span class="w"> </span><span class="err">namespace</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Conditions specify the <code class="language-plaintext highlighter-rouge">apiVersion</code>, <code class="language-plaintext highlighter-rouge">kind</code>, and <code class="language-plaintext highlighter-rouge">name</code> to identify the resource that the condition is referencing. The <code class="language-plaintext highlighter-rouge">namespace</code> property is used to specify the namespace of the resource. When omitted, the pod’s namespace is used. To target cluster scoped resources specify the <code class="language-plaintext highlighter-rouge">"namespaced": false</code> property.</p>

<p><em>The schema for the annotation value of a condition can be found <a href="https://github.com/ksgate/ksgate/blob/main/condition.schema.json">here</a>.</em></p>

<h3 id="how-conditions-work">How Conditions Work</h3>

<p>The KSGate controller identifies any workloads that are <code class="language-plaintext highlighter-rouge">schedulingGated</code> and identifies any matching gates. For each matching gate found a watcher is created that will receive any events for the referenced resource and when the condition is met the gate is removed.</p>

<h3 id="expressions">Expressions</h3>

<p><strong>Expressions</strong> are what give real power to conditions.</p>

<p>The <code class="language-plaintext highlighter-rouge">expression</code> property on a condition is used to specify a <a href="https://kubernetes.io/docs/reference/using-api/cel/">CEL</a> expression that must evaluate to true for the condition to be satisfied.</p>

<p>Here’s an example of a condition that uses an expression to define a dependency on the Pod named <code class="language-plaintext highlighter-rouge">database</code> being in the <code class="language-plaintext highlighter-rouge">Running</code> phase:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"apiVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"v1"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">(optional)</span><span class="w"> </span><span class="err">defaults</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="s2">"v1"</span><span class="p">,</span><span class="w"> </span><span class="err">as</span><span class="w"> </span><span class="err">in</span><span class="w"> </span><span class="err">core/v</span><span class="mi">1</span><span class="w">
  </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Pod"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">required</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"database"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">required</span><span class="w">
  </span><span class="nl">"namespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"default"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">(optional)</span><span class="w"> </span><span class="err">defaults</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">resources's</span><span class="w"> </span><span class="err">namespace</span><span class="w">
  </span><span class="nl">"expression"</span><span class="p">:</span><span class="w"> </span><span class="s2">"resource.status.phase == 'Running'"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">(optional)</span><span class="w"> </span><span class="err">if</span><span class="w"> </span><span class="err">omitted</span><span class="p">,</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">existence</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">resource</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">used</span><span class="w"> </span><span class="err">to</span><span class="w"> </span><span class="err">satisfy</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">condition</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The CEL environment contains two variables; <code class="language-plaintext highlighter-rouge">resource</code> and <code class="language-plaintext highlighter-rouge">this</code>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">resource</code> - gives access to the resource matched by the <code class="language-plaintext highlighter-rouge">apiVersion</code>, <code class="language-plaintext highlighter-rouge">kind</code>, and <code class="language-plaintext highlighter-rouge">name</code> properties</li>
  <li><code class="language-plaintext highlighter-rouge">this</code> - gives access to the pod that is being scheduled.</li>
</ul>

<p>A single function also extends the standard set of functions:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">now()</code> - gives access to the current time so temporal expressions can be used. (<em>e.g. <code class="language-plaintext highlighter-rouge">(now() - timestamp(resource.metadata.creationTimestamp)) &gt; duration('10s')</code> means that the resources was created at least 10 seconds before now</em>)</li>
</ul>

<p><strong>CEL</strong> expressions allow for complex conditions to be specified over the entire content of the pod (using <code class="language-plaintext highlighter-rouge">this</code>) and the target <code class="language-plaintext highlighter-rouge">resource</code>.</p>

<h3 id="example">Example</h3>

<p>Let’s see our original example with <strong>KSGate</strong>. You’ll recall that we had a database workload and a secret that the database workload depended on.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">database</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">k8s.ksgate.org/db-secret</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">{</span>
        <span class="s">"kind": "Secret",</span>
        <span class="s">"name": "db-secret"</span>
      <span class="s">}</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">database</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">postgres</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
  <span class="na">schedulingGates</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">k8s.ksgate.org/db-secret</span>
</code></pre></div></div>

<p>Now the database pod has a scheduling gate for the secret and it won’t be scheduled until the secret exists, perhaps indefinitely and without any resource waste but will immediately be scheduled when the secret arrives.</p>

<p>Suppose we include the application such that it depends both on the secret and the database.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">k8s.ksgate.org/db-pod</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">{</span>
        <span class="s">"kind": "Pod",</span>
        <span class="s">"name": "database",</span>
        <span class="s">"expression": "resource.status.phase == 'Running'"</span>
      <span class="s">}</span>
    <span class="na">k8s.ksgate.org/db-secret</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">{</span>
        <span class="s">"kind": "Secret",</span>
        <span class="s">"name": "db-secret"</span>
      <span class="s">}</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">application</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">nginx</span>
    <span class="na">env</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_HOST</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">database</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">DB_PORT</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">5432"</span>
    <span class="na">envFrom</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">secretRef</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">db-secret</span>
  <span class="na">schedulingGates</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">k8s.ksgate.org/database</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">k8s.ksgate.org/db-secret</span>
</code></pre></div></div>

<p>Pretty neat, right?</p>

<h3 id="closing-thoughts">Closing thoughts</h3>

<p><strong>KSGate</strong> is a controller that enables you to define scheduling gates and the conditions under which they should be removed in a declarative way using powerful CEL expressions. In doing so it allows you to define scalable, indefinite, resource free and failure free dependencies. It can also reduce those wasteful scheduling scenarios and hopefully reduce costs.</p>

<p>I’m excited to see where this goes and I’m looking forward to seeing what other people think about this idea. To that end, I’ve enabled <a href="https://github.com/ksgate/ksgate/discussions">GitHub Discussions</a> for the project where you can give feedback and share your thoughts and ideas. If you’re interested in contributing to the project, please feel free to reach out to me there.</p>

<p><strong>KSGate</strong> is licensed under the permissible <strong><a href="https://github.com/ksgate/ksgate/blob/main/LICENSE">Apache License 2.0</a></strong>.</p>

<p>If you care to leave feedback, please do so on <a href="https://www.linkedin.com/in/raymond-auge/">LinkedIn</a>.</p>]]></content><author><name>Raymond Augé</name></author><category term="kubernetes" /><category term="ksgate" /><category term="cel" /><category term="resource-management" /><summary type="html"><![CDATA[Recently, there's a ton of energy spent in Kubernetes land thinking about how to save costs. Maybe KSGate can help!]]></summary></entry><entry><title type="html">Best One Line Kubernetes Development Environment</title><link href="https://rotty3000.doublebite.com/Simple-K8s-Helm-localdev/" rel="alternate" type="text/html" title="Best One Line Kubernetes Development Environment" /><published>2025-03-06T00:00:00+00:00</published><updated>2025-03-06T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/Simple-K8s-Helm-localdev</id><content type="html" xml:base="https://rotty3000.doublebite.com/Simple-K8s-Helm-localdev/"><![CDATA[<p>If anyone is interested in developing their Kubernetes (and say Helm) skills, let me show you the best one line Kubernetes development environment setup I’ve found to date.</p>

<p>First the tools you need to install:</p>

<ul>
  <li><a href="https://docs.docker.com/engine/install/">Docker</a></li>
  <li><a href="https://k3d.io/stable/#releases">k3d</a></li>
  <li><a href="https://kubernetes.io/docs/tasks/tools/#kubectl">kubectl</a></li>
</ul>

<p><strong>Now here’s the magic one-liner:</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>k3d cluster create mycluster <span class="nt">-p</span> 80:80@loadbalancer
</code></pre></div></div>

<p>Ok, that looks fairly innocent.</p>

<p><em>“What is the advantage of this over other options?”</em> You ask.</p>

<p>Well, there are a couple of secrets at work here that took me a while to put together.</p>

<p>But let’s talk first about one of the biggest pains with doing any kind of microservices development locally; <em>hostname resolution</em>.</p>

<h3 id="local-hostname-resolution">Local Hostname Resolution</h3>

<p>Most of the time when you have any number of microservices beyond 2 (and even 2) you need to resolve hostnames. IP addresses are hard to remember and unique port numbers are a pain.</p>

<p>More importantly you can quickly loose affinity towards the networking aspects of production particularly necessary when working with web applications such as <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Content Security Policies</a>, authorization server front channel and back channel flows, and other things that are a pain or impossible with <code class="language-plaintext highlighter-rouge">localhost</code>. Particularly you’d like to actually test those concerns out locally if possible.</p>

<p>What most people do is modify the <code class="language-plaintext highlighter-rouge">hosts</code> file on their local system. But this requires root or admin access. The problem with this is that it ranges from impossible on some locked down systems to just a huge pain.</p>

<h3 id="secret-1-docker-hostname-resolution">Secret #1: Docker Hostname Resolution</h3>

<p>Docker (at least on Linux and MacOS) has a solution for this in that it <em>automatically</em> resolves any hostname with the suffix <code class="language-plaintext highlighter-rouge">.docker.localhost</code> to the IP address of the Docker host.</p>

<p>So, for example, if you have a service running on your local system on port <code class="language-plaintext highlighter-rouge">8080</code> and you want to access it using <code class="language-plaintext highlighter-rouge">http://myservice.docker.localhost:8080</code>, it will work.</p>

<p>The ability to use an arbitrary number of hostnames without any fuss is really useful.</p>

<h3 id="secret-2-k3d-load-balancer--port-forwarding-port-80">Secret #2: k3d Load Balancer &amp; Port Forwarding Port 80</h3>

<p>Our K3d command causes K3D to set up a Load Balancer (<a href="https://k3d.io/v5.5.1/usage/k3s/#traefik-in-k3d">traefik</a>) service running in a docker bridge network created for your cluster. It then sets up port forwarding from your local system’s port <code class="language-plaintext highlighter-rouge">80</code> to that Load Balancer service.</p>

<p>Now the really nice part is that it can actually forward port <code class="language-plaintext highlighter-rouge">80</code> which normally is a privilege that requires root or admin access. Luckily the Docker infrastructure enables this without any fuss. This means we can work with standard HTTP ports and not have to worry about unique port numbers messing up our configurations.</p>

<p>(<em><strong>Note:</strong> If you want enable HTTPS it’s a little more complicated, not much, but to get the networking setup all you’d need to add is <code class="language-plaintext highlighter-rouge">-P 443:443@loadbalancer</code> to our one-liner.</em>)</p>

<p>The built in Load Balancer service allows you create Kubernetes <code class="language-plaintext highlighter-rouge">Ingress</code> resources the way you would in a production Kubernetes cluster.</p>

<p>For example, if you have a Kubernetes service running on port <code class="language-plaintext highlighter-rouge">80</code> and you create an <code class="language-plaintext highlighter-rouge">Ingress</code> resource with hostname <code class="language-plaintext highlighter-rouge">myservice.docker.localhost</code> and you want to access it using <code class="language-plaintext highlighter-rouge">http://myservice.docker.localhost</code> (note I omitted the port) it will work!</p>

<h3 id="want-to-test-lets-go">Want to test? Let’s Go!</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create our cluster</span>
k3d cluster create mycluster <span class="nt">-p</span> 80:80@loadbalancer

<span class="c"># Create a deployment</span>
kubectl create deployment nginx <span class="nt">--image</span><span class="o">=</span>nginx

<span class="c"># Expose the deployment through a service</span>
kubectl expose deployment nginx <span class="nt">--port</span><span class="o">=</span>80 <span class="nt">--type</span><span class="o">=</span>ClusterIP <span class="nt">--name</span><span class="o">=</span>nginx-service

<span class="c"># Create an ingress resource with a hostname that routes to the service</span>
kubectl create ingress nginx-ingress <span class="nt">--rule</span><span class="o">=</span><span class="s2">"myservice.docker.localhost/*=nginx-service:80"</span>
</code></pre></div></div>

<p>Now you can access your service at <code class="language-plaintext highlighter-rouge">http://myservice.docker.localhost</code></p>

<p><img src="/assets/images/Simple-K8s-Helm-localdev/nginx.png" alt="nginx" /></p>

<h3 id="test-helm-with-ingress">Test Helm with Ingress</h3>

<p>Let’s install Keycloak using Helm and make it accessible at <code class="language-plaintext highlighter-rouge">http://keycloak.docker.localhost</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create our cluster</span>
k3d cluster create mycluster <span class="nt">-p</span> 80:80@loadbalancer

<span class="c"># Add the Helm repository</span>
helm repo add bitnami https://charts.bitnami.com/bitnami

<span class="c"># Install the Keycloak chart</span>
helm <span class="nb">install </span>keycloak bitnami/keycloak <span class="nt">--set</span> ingress.enabled<span class="o">=</span><span class="nb">true</span> <span class="nt">--set</span> ingress.hostname<span class="o">=</span>keycloak.docker.localhost <span class="nt">--set</span> auth.adminPassword<span class="o">=</span>admin
</code></pre></div></div>

<p>Now you can access keycloak at <code class="language-plaintext highlighter-rouge">http://keycloak.docker.localhost</code></p>

<p><img src="/assets/images/Simple-K8s-Helm-localdev/keycloak.png" alt="keycloak" /></p>

<p>Easy peasy!</p>

<p>If you care to leave feedback, or if you know of an even easier one liner that gives you this much functionality, please do so on <a href="https://www.linkedin.com/in/raymond-auge/">LinkedIn</a>.</p>

<p>Thanks for stopping by!</p>]]></content><author><name>Raymond Augé</name></author><category term="kubernetes" /><category term="docker" /><category term="k3d" /><summary type="html"><![CDATA[This is the best one line Kubernetes development environment for local development I've found to date.]]></summary></entry><entry><title type="html">It’s a Journey</title><link href="https://rotty3000.doublebite.com/It-is-a-Journey/" rel="alternate" type="text/html" title="It’s a Journey" /><published>2025-03-01T00:00:00+00:00</published><updated>2025-03-01T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/It-is-a-Journey</id><content type="html" xml:base="https://rotty3000.doublebite.com/It-is-a-Journey/"><![CDATA[<p>It’s been a long time since I’ve written publicly. I’ve been busy with work and life. I’m sure everyone can relate.</p>

<p>I was going to dive right into technical details, but I think it’s important to understand the why behind the what so I want to start with a brief overview of my recent journey.</p>

<p>The bulk of my career has been spent in Java. My beloved language where I spent the first 15 years of my career honing my skills every day; to this day is still a comfort zone for me.</p>

<p>I’ve been using it since the early 2000s where it was the main language for the curriculum at my university and while I’ve loved taking side roads into other languages and technologies, I’ve always come back to Java.</p>

<p>What I enjoyed so much about Java was, and is, the vastness of the ecosystem. The JVM is a powerful runtime that allows me to build performant applications. And the landscape of tools and libraries is so vast and prolific that I could always find something to help me build the right thing. The development experience was second to none for so long. Few language ecosystems could boast such a productive set of tools and features that allowed huge teams to collaborate on the same codebase. Starting from IDEs, build tools, testing frameworks, debugging tools, analytics tools, and so much more, the ecosystem is mature and full of options.</p>

<p>It’s even hard to believe a day would come where I would opt for anything else… and yet here we are. But I have to admit the choice was kind of made for me in a sense.</p>

<p>Over the course of 2 decades I worked mostly in Java on a web centric enterprise application. A monolithic application with millions of lines of code, an engineering team well in the hundreds, having countless features and integrations.</p>

<p>These days however, I’ve been working on “Cloud Native” applications; applications that are built to run in infrastructure that is not under my control. Bare metal barely exists today and even where it does its use is almost exclusively buried deep under layers of abstraction. Even on my local machine I’m using Docker or Kubernetes (likely both) to run my applications.</p>

<p>I’m working a lot with Kubernetes. And, it just so happens that the defacto language for the Kubernetes ecosystem is Go; and the most interesting work being done in the Kubernetes ecosystem is being done in Go.</p>

<p>The Go ecosystem is already very mature and the tooling is very good. What I like very much about Go is the simplicity of the developer experience. You immediately have opinionated tooling for building, testing, packaging and running your application. The cross compilation story is so easy that I don’t even think about it. Dependency management is just as easy and assembling an application from multiple packages is a breeze. I can easily debug both directly and remotely launched applications. I can write powerful tests, I can execute them in seconds and I can effortlessly verify code coverage. I can write complex applications and build them into a statically linked binary of only a few megabytes or I can package that binary into a container of only a few megabytes (most of the applications I’ve worked on have resulted in images of less than 15Mb).</p>

<p>All of this I can do within a matter of minutes after I’ve run <code class="language-plaintext highlighter-rouge">go mod init my/app</code> on a new project.</p>

<p>The language itself is simple enough for my brain to process quickly. And since I’m not enough of a language lawyer to care about the language features too much, I can focus on building my application. I’ve gotten used to the error handling and the concurrency model and honestly I rather like them. I am by no means a Go expert, but I’ve been able to pick it up quickly and apply it to build some really cool stuff. Of note are the HTTP server primitives in the standard library which are so good that I’m doing complex things in a matter of minutes.</p>

<p>Go is great both for building system tools and for building web applications and since those two places are where I live, I’m happy.</p>

<p>If you care to leave feedback, please do so on <a href="https://www.linkedin.com/in/raymond-auge/">LinkedIn</a>.</p>

<p>Again, I hope to write more about what I’ve been doing and what I’ve learned in the coming weeks.</p>

<p>Thanks for stopping by!</p>]]></content><author><name>Raymond Augé</name></author><category term="kubernetes" /><category term="go" /><summary type="html"><![CDATA[About my recent transition from mostly Java to mostly Kubernetes and Go.]]></summary></entry><entry><title type="html">OSGi On Kubernetes - Configuration</title><link href="https://rotty3000.doublebite.com/OSGi-On-Kubernetes-Configuration/" rel="alternate" type="text/html" title="OSGi On Kubernetes - Configuration" /><published>2021-06-13T00:00:00+00:00</published><updated>2021-06-13T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/OSGi-On-Kubernetes-Configuration</id><content type="html" xml:base="https://rotty3000.doublebite.com/OSGi-On-Kubernetes-Configuration/"><![CDATA[<p>Configuration is an important aspect in application design and especially in the design of Cloud applications. The need to develop, test, and release code in the ephemeral spaces of the Cloud mean that many important details about environment and behaviour need to be abstracted away from the application logic.</p>

<p>Many years before Cloud environments existed <a href="https://www.osgi.org/">OSGi</a> technology was being leveraged to deploy complex, distributed applications to countless devices. Due in large part to its resilience and innovative design it continues to be used in this fashion today. Relatively early in OSGi’s long history the <a href="https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.cm.html">Configuration Admin Service Specification</a> was defined with much the same concerns for configuration as are present in current environments like today’s Cloud.</p>

<p><a href="https://kubernetes.io">Kubernetes</a> is a very popular and widely adopted technology for orchestrating applications in the Cloud and is provided by a growing number of the largest Cloud vendors. It also defines a set of core components that are responsible for keeping applications well fed with configuration.</p>

<p>Notably, Kubernetes configuration principles very closely resemble those of OSGi Configuration making integration quite natural which is the topic of this post.</p>

<h2 id="defining-configmaps">Defining ConfigMaps</h2>

<p>The first Kubernetes configuration component is called <a href="https://kubernetes.io/docs/concepts/configuration/configmap/">ConfigMap</a> and represents the basic building block for providing non-confidential data in key-value pairs.</p>

<p><em>(Don’t place secure or data that requires encryption in ConfigMaps. Another Kubernetes component we’re going to talk about later is intended for that.)</em></p>

<p>A ConfigMap is defined in a Kubernetes manifest file (commonly using the <code class="language-plaintext highlighter-rouge">YAML</code> syntax) like the following:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ConfigMap</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="c1"># property-like keys; each key maps to a simple value</span>
  <span class="na">pool_initial_size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3"</span>
  <span class="na">pool_max_size</span><span class="pi">:</span> <span class="s2">"</span><span class="s">10"</span>

  <span class="c1"># file-like keys</span>
  <span class="na">message.processor-email.config</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">pool.initial.size=i"${env:pool_initial_size}"</span>
<span class="err">	</span><span class="s">pool.max.size=i"${env:pool_max_size}"</span>
    <span class="s">topic="/email"</span>
  <span class="s">message.processor-log.config</span><span class="err">:</span> <span class="pi">|</span>
    <span class="s">pool.initial.size=i"${env:pool_initial_size}"</span>
<span class="err">	</span><span class="s">pool.max.size=i"20"</span>
    <span class="s">topic="/log"</span>
</code></pre></div></div>

<p><em>(We’ll be seeing a lot of <code class="language-plaintext highlighter-rouge">YAML</code> in this and future posts so take heart.)</em></p>

<p>The example above demonstrates the basic model for ConfigMaps. The first 4 lines are boiler plate and are setup for the <code class="language-plaintext highlighter-rouge">data</code> field which holds a series of key-value pairs in one of two flavours;</p>

<ul>
  <li><strong>property-like keys</strong> - typical key mapping to a single value</li>
  <li><strong>file like keys</strong> - maps a key (typically with a file-like name) to a multi-line chunk of data (that looks a lot like a file)</li>
</ul>

<p><strong><em>Take note</em></strong> <em>of the syntax used in the file content. We’ll come back to that later.</em></p>

<p>The next thing you should note about the value of the <em>file-like-keys</em> flavour is that the format of the value is largely irrelevant. The ConfigMap does not care one way or the other if it’s XML, JSON, Java properties files, more YAML, or any other character based syntax you wish (I supposed it could even be script… but I’d be careful with that). The only real restriction is that a given ConfigMap’s <code class="language-plaintext highlighter-rouge">data</code> does not exceed 1 <a href="https://simple.wikipedia.org/wiki/Mebibyte">MiB</a> in size.</p>

<h2 id="consuming-configmaps">Consuming ConfigMaps</h2>

<p>There are several ways this could be used but we’ll talk about the 2 most common.</p>

<p>Consider the following <a href="https://kubernetes.io/docs/concepts/workloads/pods/">Pod</a> spec.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-pod</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-container</span>
      <span class="na">image</span><span class="pi">:</span> <span class="s">rotty3000/config-osgi-k8s-demo</span>
      <span class="na">env</span><span class="pi">:</span>
        <span class="c1"># Define environment variables</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">pool_initial_size</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">configMapKeyRef</span><span class="pi">:</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span> <span class="c1"># The ConfigMap this value comes from</span>
              <span class="na">key</span><span class="pi">:</span> <span class="s">pool_initial_size</span> <span class="c1"># The key to fetch</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">pool_max_size</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">configMapKeyRef</span><span class="pi">:</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span>
              <span class="na">key</span><span class="pi">:</span> <span class="s">pool_max_size</span>
      <span class="na">volumeMounts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
        <span class="na">mountPath</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/app/configs"</span>
        <span class="na">readOnly</span><span class="pi">:</span> <span class="no">true</span>
  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
      <span class="na">configMap</span><span class="pi">:</span>
        <span class="c1"># The name of the ConfigMap you want to mount</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span>
</code></pre></div></div>

<p><em>(Pods are the smallest deployable units of computing in a Kubernetes cluster.)</em></p>

<p>The example above defines a Pod, the first 5 lines of which are pretty boiler plate, named <code class="language-plaintext highlighter-rouge">osgi-demo-pod</code>, has exactly one <code class="language-plaintext highlighter-rouge">container</code> named <code class="language-plaintext highlighter-rouge">osgi-demo-container</code>. The container specifies the Docker image to be <code class="language-plaintext highlighter-rouge">rotty3000/config-osgi-k8s-demo</code>. That alone is the basic information necessary to define a usable Pod. You could deploy this and have a running system:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-pod</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">containers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-container</span>
      <span class="na">image</span><span class="pi">:</span> <span class="s">rotty3000/config-osgi-k8s-demo</span>
</code></pre></div></div>

<p><em>(Though <a href="https://kubernetes.io/docs/concepts/configuration/overview/#naked-pods-vs-replicasets-deployments-and-jobs">Naked Pods</a> are discouraged, they are useful for illustration.)</em></p>

<p>However, this is not sufficient in the majority of cases. Usually some additional input is required, such as resource constraints, volume mounts, configuration, etc.</p>

<p>Let’s start our examination at the bottom of the spec with the <code class="language-plaintext highlighter-rouge">volumes</code> field. <a href="https://kubernetes.io/docs/concepts/storage/volumes/">Volumes</a> are Kubernetes’ mechanism for sharing file systems that supplement a Pod’s own ephemeral file system (ephemeral meaning nothing that is modified in the Pod’s own file system is retained across Pod restarts). There are many different types of Volumes in Kubernetes. The one we’re showing here adds a ConfigMap:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
      <span class="na">configMap</span><span class="pi">:</span>
        <span class="c1"># The name of the ConfigMap you want to mount</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span>
</code></pre></div></div>

<p>In order for the ConfigMap to be usable it must be mounted as a volume, so we define the volume by first giving it a name (<code class="language-plaintext highlighter-rouge">config</code>), by specifying a Volume <em>type</em> in this case a <code class="language-plaintext highlighter-rouge">configMap</code> named (<code class="language-plaintext highlighter-rouge">osgi-demo-config</code>).</p>

<p><em>(The Pod and the ConfigMap have to be in the same <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/">Namespace</a>.)</em></p>

<p>Now that the volume is defined we can <em>mount</em> it into the pod using the <code class="language-plaintext highlighter-rouge">volumeMounts</code> field under the <code class="language-plaintext highlighter-rouge">containers</code> array:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="na">volumeMounts</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
        <span class="na">mountPath</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/app/configs"</span>
        <span class="na">readOnly</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>

<p>The mount is set to be located at <code class="language-plaintext highlighter-rouge">/app/configs</code>. In this case the image we’re using already listens to this directory using <a href="https://felix.apache.org/documentation/subprojects/apache-felix-file-install.html">Apache Felix FileInstall</a>; an OSGi bundle that manages OSGi configurations from the file system on demand. A perfect companion for Kubernetes ConfigMaps. FileInstall is configured to do this using the system property <code class="language-plaintext highlighter-rouge">felix.fileinstall.dir=/app/configs</code>.</p>

<h3 id="mounted-files">Mounted files</h3>

<p>You should note that as configured the volume will cause <em>each key</em> in the ConfigMap to be mounted as a separate file in the mount directory (the value of each pair being the contents of the file). This is great because FileInstall automatically detects and manages files that end with <code class="language-plaintext highlighter-rouge">.config</code> (or the older <code class="language-plaintext highlighter-rouge">.cfg</code>) identified in it’s scanned directory	. <em>(Unknown files will be ignored.)</em></p>

<h4 id="mounting-specific-entries">Mounting specific entries</h4>

<p>You <em>can</em> pick and choose which keys of the ConfigMap are loaded as files by adding an <code class="language-plaintext highlighter-rouge">items</code> array:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">volumes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">config</span>
      <span class="na">configMap</span><span class="pi">:</span>
        <span class="c1"># The name of the ConfigMap you want to mount</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span>
        <span class="c1"># An array of keys from the ConfigMap to create as files</span>
        <span class="na">items</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">message.processor-email.config"</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">message.processor-email.config"</span>
        <span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">message.processor-log.config"</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">message.processor-log.config"</span>
</code></pre></div></div>

<p>However; I don’t recommend the <code class="language-plaintext highlighter-rouge">items</code> approach in the OSGi Configuration scenario in the default case because it forces a duplication of the same information in two places; the ConfigMap and the <code class="language-plaintext highlighter-rouge">volumes</code> definition. I suggest lettings the ConfigMap be the source of truth and I’ll explain why a little later.</p>

<h4 id="sub-directory-scans">Sub-directory scans</h4>

<p>By default FileInstall scans sub-directories as well, so create any hierarchy that suites your needs by mounting the ConfigMap in any sub-directory of the path scanned by FileInstall. This is handy for grouping related configuration files.</p>

<h3 id="environment-variables">Environment Variables</h3>

<p>Another way to use the key-value pairs of the ConfigMap in the Pod is by defining environment variables. If you go back to the <code class="language-plaintext highlighter-rouge">env</code> field of the Pod spec you’ll see the following:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="na">env</span><span class="pi">:</span>
        <span class="c1"># Define environment variables</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">pool_initial_size</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">configMapKeyRef</span><span class="pi">:</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span> <span class="c1"># The ConfigMap this value comes from</span>
              <span class="na">key</span><span class="pi">:</span> <span class="s">pool_initial_size</span> <span class="c1"># The key to use</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">pool_max_size</span>
          <span class="na">valueFrom</span><span class="pi">:</span>
            <span class="na">configMapKeyRef</span><span class="pi">:</span>
              <span class="na">name</span><span class="pi">:</span> <span class="s">osgi-demo-config</span>
              <span class="na">key</span><span class="pi">:</span> <span class="s">pool_max_size</span>
</code></pre></div></div>

<p>Here, two of the keys are cherry-picked from the ConfigMap and turned into environment variables that will be available in the Pod from the moment of startup.</p>

<p>Interestingly, if you recall the note about the syntax of the configuration files content which I’ve replicated next,</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">message.processor-email.config</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">pool.initial.size=i"${env:pool_initial_size}"</span>
<span class="err">	</span><span class="s">pool.max.size=i"${env:pool_max_size}"</span>
    <span class="s">topic="/email"</span>
</code></pre></div></div>

<p>you should be aware that FileInstall has built in interpolation for environment variables using the <code class="language-plaintext highlighter-rouge">env:</code> prefix. This allows you to compose configuration using the environment with no additional tooling installed.</p>

<h3 id="advanced-interpolation-using-mounted-files">Advanced interpolation using mounted files</h3>

<p>Remember those <em>property-like keys</em> we discussed earlier? As was just stated a ConfigMap volume will cause these to be mounted as individual files. FileInstall will gladly ignore them, however it could be interesting, particularly if the contents of the directory comes from different ConfigMaps, to have a more advanced interpolation mechanism that would allow those to be used as a source of values.</p>

<p>As it turns out the Apache Felix project provides a companion bundle that adds exactly this functionality via a Configuration Plugin. This bundle is called <a href="https://github.com/apache/felix-dev/tree/master/configadmin-plugins/interpolation"><code class="language-plaintext highlighter-rouge">org.apache.felix.configadmin.plugin.interpolation</code></a> and it’s already part of the base image we’re using in the examples.</p>

<p>We configure the Felix interpolation plugin using system properties.</p>

<p>The first property tells Felix Config Admin (the implementation of OSGi Configuration Admin) to require the plugin before processing any configuration:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">felix.cm.config.plugins</span><span class="p">=</span><span class="s">org.apache.felix.configadmin.plugin.interpolation</span>
</code></pre></div></div>

<p>This second property tells the plugin which directory to search for values:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">org.apache.felix.configadmin.plugin.interpolation.secretsdir</span><span class="p">=</span><span class="s">/app/configs</span>
</code></pre></div></div>

<p>With that in mind, consider the 2 following data sections from 2 separate ConfigMaps:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">## ConfigMap #1</span>
<span class="c1"># assume this is mounted as `/app/configs/values`</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">player_initial_lives</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3"</span>
</code></pre></div></div>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">## ConfigMap #2</span>
<span class="c1"># assume this is mounted as `/app/configs/files`</span>
<span class="na">data</span><span class="pi">:</span>
  <span class="na">game.pid.config</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">player.initial.lives="$[secret:values/player_initial_lives;type=long]"</span>
    <span class="s">player.maximum.lives=i"5"</span>
    <span class="s">colors="$[secret:colors;type=String[];delimiter=|;default=green|red|blue]"</span>
</code></pre></div></div>

<p>As you can see with the syntax provided by the Felix interpolation plugin allows us to refer to ConfigMap files as interpolation values.</p>

<p>You’ll also note that Felix interpolation plugin has more advanced capabilities like <strong>rich type coercion</strong> and the ability to <strong>provide defaults for missing values</strong>.</p>

<h3 id="dynamic-reaction-to-configuration-changes">Dynamic reaction to configuration changes</h3>

<p>Many software systems are capable of gracefully handling changes in configuration without requiring full restarts. For example <a href="https://httpd.apache.org">Apache HTTPD Server</a> can use it’s <a href="https://httpd.apache.org/docs/current/stopping.html#graceful"><code class="language-plaintext highlighter-rouge">graceful</code></a> restart mode to smoothly reload it’s configuration and automatically adjust its behaviour accordingly. <a href="https://docs.nginx.com/nginx/admin-guide/basic-functionality/runtime-control/">NGINX</a> offers a similar functionality and so do many other systems.</p>

<p>Besides the obvious goal of reducing service interruptions this also supports the notion of feature flags, service discovery and other real-time signals required to efficiently and speedily control system behaviour.</p>

<h3 id="configmaps-as-mounted-files-vs-environment-variables">ConfigMaps as mounted files vs. environment variables</h3>

<p>Given that <a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically">mounted ConfigMaps are updated automatically</a> without needing to restart the Pod vs. environment variable which require a Pod be restarted in order to be updated means that mounted files are more efficient for systems that are designed to be signalled on configuration file changes.</p>

<h2 id="conclusion">Conclusion</h2>

<p><strong>OSGi Configuration Admin</strong> is meant to dynamically propagate configuration changes throughout an OSGi framework. <strong>Apache Felix FileInstall</strong> adds to that the ability to react to changes in configuration resources on the file system which are subsequently propagated to Configuration Admin. <strong>Kubernetes ConfigMaps</strong> provide Pods with centrally and dynamically updated configuration resources.</p>

<p>The combination of the three makes for a completely dynamic and fully integrated application configuration system with very little effort.</p>

<p>In a future post I plan to further various aspects of this subject. So stay tuned!</p>

<h3 id="test-resources">Test Resources</h3>

<p>The project demonstrating the ideas in this post can be found in <a href="https://github.com/rotty3000/osgi-config-aff">this repository</a>. The docker image that defines the configurable OSGi application can be found <a href="https://hub.docker.com/r/rotty3000/config-osgi-k8s-demo">here</a> on Docker Hub.</p>]]></content><author><name>Raymond Augé</name></author><summary type="html"><![CDATA[Supercharge your Kubernetes apps with real-time configuration updates using OSGi Configuration Admin.]]></summary></entry><entry><title type="html">Adding JavaScript build to a Liferay Workspace OSGi module build</title><link href="https://rotty3000.doublebite.com/javascript-build-in-liferay-workspace-module/" rel="alternate" type="text/html" title="Adding JavaScript build to a Liferay Workspace OSGi module build" /><published>2021-03-25T00:00:00+00:00</published><updated>2021-03-25T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/javascript-build-in-liferay-workspace-module</id><content type="html" xml:base="https://rotty3000.doublebite.com/javascript-build-in-liferay-workspace-module/"><![CDATA[<p>Over the past several years I’ve been challenged more frequently with working with JavaScript. I’m not a JavaScript expert by any means so often knowledge eludes me and I feel like an outsider at trying to grok it all.</p>

<p>Recently however, the degree to which I’ve had to immerse myself has lead to more understanding. With that in mind, this article is for JavaScript beginners, like myself, trying to be productive on the outer rim of the JavaScript universe.</p>

<p>Today’s challenge starts with Liferay Workspace but what I should start by clarifying is that I am not talking about a <a href="https://help.liferay.com/hc/en-us/articles/360028832852-liferay-npm-bundler">Liferay NPM Bundler</a> type of build, nor one that produces output intended to integrate (per say) with the <a href="https://help.liferay.com/hc/en-us/articles/360029314891-JavaScript-Module-Loaders">Liferay JavaScript Module Loaders</a>. Those procedures result in output that is proprietary to Liferay and therefore cannot work with pure JavaScript consumers (such as you’d expect to find within things like third party web components or SPAs of various types.) So the idea here is to produce JavaScript output that is <em>not</em> proprietary to Liferay.</p>

<p>Secondly, I should clarify that I am not suggesting that Liferay’s JavaScript tooling is unnecessary. The tooling is both needed and great at addressing many concerns that need to be taken into consideration when participating in an ecosystem of a vast product like <strong>Liferay DXP</strong>. However, sometimes you just have other constraints. That’s the perspective that is driving this discussion.</p>

<p>So, with all that out of the way, let’s get started with a basic Liferay Workspace project setup.</p>

<p>You can follow along by looking at the <a href="https://github.com/rotty3000/com.github.rotty3000.workspace/tree/adding-javascript-build">adding-javascript-build</a> branch of my workspace <strong>Github</strong> project. In this procedure I’m going to start with a new workspace to outline all the steps to get started.</p>

<h3 id="the-workspace">The workspace</h3>

<p>Make sure that you’ve got <a href="https://help.liferay.com/hc/en-us/articles/360017885232-Installing-Blade-CLI-">blade cli</a> installed.</p>

<p>Create (init) a new workspace by executing:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>blade init <span class="nt">-v</span> portal-7.2-ga2 &lt;name of workspace&gt;
</code></pre></div></div>

<p><em>(You can see a list of the most recent versions of every Liferay Portal product available by executing <code class="language-plaintext highlighter-rouge">blade init -l</code>. And if you need an exhaustive list you can use <code class="language-plaintext highlighter-rouge">blade init -l --all</code>)</em></p>

<p>Once the workspace is created I always recommend verifying the very latest version of the workspace plugin is specified in the <code class="language-plaintext highlighter-rouge">settings.gradle</code> file in the workspace root. To find the latest version of the workspace plugin look <a href="https://search.maven.org/artifact/com.liferay/com.liferay.gradle.plugins.workspace">here</a>.</p>

<p>At the time of this writing the change to update my workspace plugin looked like this:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> buildscript {
 	dependencies {
<span class="gd">-		classpath group: "com.liferay", name: "com.liferay.gradle.plugins.workspace", version: "3.4.2"
</span><span class="gi">+		classpath group: "com.liferay", name: "com.liferay.gradle.plugins.workspace", version: "3.4.5"
</span> 		classpath group: "net.saliman", name: "gradle-properties-plugin", version: "1.4.6"
 	} 
</code></pre></div></div>

<p>Next let’s test that the workspace is properly configured by executing</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew initBundle
</code></pre></div></div>

<p>Sure there are no modules to speak of but performing this action downloads the workspace specified version of gradle, if not already downloaded, it retrieves all the dependencies for a slew of default plugins with which the Liferay Workspace come pre-configured and finally it downloads the bundle of the Liferay version specified.</p>

<p>With the Liferay bundle initialised and assuming you’ve not changed any other workspace settings (in <code class="language-plaintext highlighter-rouge">gradle.properties</code>) it should now be possible to start a running instance of Liferay portal by executing the command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundles/tomcat-<span class="k">${</span><span class="nv">tomcat_version</span><span class="k">}</span>/bin/catalina.sh run
</code></pre></div></div>

<p><em>(You can determine the tomcat version just by looking in the bundles directory. I usually use <code class="language-plaintext highlighter-rouge">tab</code> completion to auto-complete paths to save time.)</em></p>

<p>This command starts Tomcat (<em>and Liferay</em>) in the foreground with logs outputted to <code class="language-plaintext highlighter-rouge">stdout</code>. You can stop  Tomcat and Liferay with <code class="language-plaintext highlighter-rouge">ctrl-D</code>.</p>

<p>Now personally, I invariably end up writing code that needs to be debugged. This means I end up needing to run Liferay portal in debug mode. Thankfully this is trivial with Tomcat’s built in debug launch mode. Just add <code class="language-plaintext highlighter-rouge">jpda</code> to the command above before the <code class="language-plaintext highlighter-rouge">run</code> argument. The full command ends up being</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundles/tomcat-<span class="k">${</span><span class="nv">tomcat_version</span><span class="k">}</span>/bin/catalina.sh jpda run
</code></pre></div></div>

<p>and Tomcat starts up with remote debugging enabled on port <code class="language-plaintext highlighter-rouge">8000</code>.</p>

<p>One nice thing about the Liferay portal bundle is that without any other configuration it will run locally with the embedded <strong>HyperSQL</strong> database and <strong>Elasticsearch</strong> search engine to make it easy to get underway.</p>

<h3 id="the-module">The module</h3>

<p>Now let’s start a new module. Start by moving into the <code class="language-plaintext highlighter-rouge">modules</code> directory of the workspace and then execute the following:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>blade create <span class="nt">-t</span> service <span class="nt">-s</span> java.util.function.Supplier adding-javascript-build
</code></pre></div></div>

<p>This is a simple <code class="language-plaintext highlighter-rouge">service</code> type project with a class that implements <code class="language-plaintext highlighter-rouge">java.util.function.Supplier</code> to be published as an <strong>OSGi</strong> service. This module is inane but demonstrative enough to get us going. We’re not focusing on the OSGi part specifically at this time but we’re setting out to prove that while we have an OSGi module we’re also going to get some JavaScript goodness too.</p>

<h3 id="the-generated-class">The generated class</h3>

<p>Let’s take a moment to touch on the class that was generated as part of the module creation. It looks like this (with comments removed):</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">adding.javascript.build</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.function.Supplier</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.osgi.service.component.annotations.Component</span><span class="o">;</span>
<span class="nd">@Component</span><span class="o">(</span>
	<span class="n">immediate</span> <span class="o">=</span> <span class="kc">true</span><span class="o">,</span>
	<span class="n">property</span> <span class="o">=</span> <span class="o">{</span>
	<span class="o">},</span>
	<span class="n">service</span> <span class="o">=</span> <span class="nc">Supplier</span><span class="o">.</span><span class="na">class</span>
<span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AddingJavascriptBuild</span> <span class="kd">implements</span> <span class="nc">Supplier</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The first thing you probably noticed is that it doesn’t even compile. This is because the template didn’t fill out any required methods for the service type we picked. Let’s fix that, and also let’s add the generic type while we’re at it, make it return something interesting, make it a <a href="https://felix.apache.org/documentation/subprojects/apache-felix-gogo.html">gogo</a> command and finally we’ll add some (de)activate output so we can see what’s happening as we (re)deploy the module:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@@ -1,11 +1,27 @@</span>
 package adding.javascript.build;
 import java.util.function.Supplier;
<span class="gi">+import org.osgi.service.component.annotations.Activate;
</span> import org.osgi.service.component.annotations.Component;
<span class="gi">+import org.osgi.service.component.annotations.Deactivate;
</span> @Component(
 	immediate = true,
 	property = {
<span class="gi">+		"osgi.command.scope=ajb",
+		"osgi.command.function=get"
</span> 	},
 	service = Supplier.class
 )
 public class AddingJavascriptBuild implements Supplier&lt;String&gt; {
<span class="gi">+	@Override
+	public String get() {
+		return "Something interesting!";
+	}
+	@Activate
+	void activate() {
+		System.out.println("Activated!");
+	}
+	@Deactivate
+	void deactivate() {
+		System.out.println("Deactivated!");
+	}
</span> }
</code></pre></div></div>

<p>Once we’ve built and deployed this module we should be able to telnet into Liferay’s embedded Gogo shell by executing <code class="language-plaintext highlighter-rouge">telnet localhost 11311</code>. Once there execute the <code class="language-plaintext highlighter-rouge">ajb:get</code> command. You should see the output of our supplier service:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">]</span><span class="nv">$ </span>telnet localhost 11311
Trying 127.0.0.1...
Connected to localhost.
Escape character is <span class="s1">'^]'</span><span class="nb">.</span>
____________________________
Welcome to Apache Felix Gogo

g! ajb:get
Something interesting!
g! 
</code></pre></div></div>

<h3 id="enable-wab">Enable WAB</h3>

<p>Next, we’re going to enable the module as a <strong>skinny WAB</strong> (Web Application Bundle). In Liferay parlance a <em>skinny WAB</em> is a <em>web application</em> that is shaped like a <strong>JAR</strong> instead of like a <strong>WAR</strong> and the only thing we need is to edit the <code class="language-plaintext highlighter-rouge">bnd.bnd</code> file that is in the root of the module and add the header <code class="language-plaintext highlighter-rouge">Web-ContextPath</code> like so:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Bundle-Name: adding-javascript-build
 Bundle-SymbolicName: adding.javascript.build
 Bundle-Version: 1.0.0
<span class="gi">+
+Web-ContextPath: /adding-javascript-build
</span></code></pre></div></div>

<p>The value of the header should contain a context path unique for our application.</p>

<p>The reason for turning our module into a WAB is so that we can serve resources. This will be essential later when we have some JavaScript to serve.</p>

<h3 id="vanilla-javascript">Vanilla JavaScript</h3>

<p>Now, if all we needed was some vanilla JavaScript code then we’d only need to place the files within the <code class="language-plaintext highlighter-rouge">src/main/resources/META-INF/resources/</code> directory of our module and that would be sufficient. The files would automatically be accessible from <code class="language-plaintext highlighter-rouge">&lt;hostname&gt;/o/adding-javascript-build/...</code> due to our module being a WAB. The <code class="language-plaintext highlighter-rouge">/o</code> is the path withing Liferay used to target OSGi modules and is added to any WAB paths. In a skinny WAB all resources under the path <code class="language-plaintext highlighter-rouge">src/main/resources/META-INF/resources/</code> (in the built JAR this translates to <code class="language-plaintext highlighter-rouge">/META-INF/resources</code>) are mapped to a default <strong>Servlet</strong> that serves them up using an appropriate mime type. Sweet right?</p>

<h3 id="advanced-javascript">Advanced JavaScript</h3>

<p>Assuming that we’re using more than vanilla JavaScript what we’ll do is initialise a JavaScript build by performing:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm init <span class="nt">-y</span>
</code></pre></div></div>

<p>This gives us a simple <code class="language-plaintext highlighter-rouge">package.json</code> file to start with in the root of our module directory.</p>

<p>Next let’s create the file <code class="language-plaintext highlighter-rouge">src/main/resources/META-INF/resources/js/index.ts</code>. The <code class="language-plaintext highlighter-rouge">.ts</code>  extension stands for <a href="https://www.typescriptlang.org/">TypeScript</a> which is a popular way these days of developing JavaScript in a type safe way (go figure ;) ). Let’s start simple:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">firstName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Rotty</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">lastName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">3000</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">role</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Programmer guy</span><span class="dl">"</span>
<span class="p">};</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">User: %O</span><span class="dl">"</span><span class="p">,</span> <span class="nx">user</span><span class="p">);</span>

<span class="k">export</span> <span class="p">{};</span>
</code></pre></div></div>

<p>Right, so now we have a source file, and we have the basic <code class="language-plaintext highlighter-rouge">package.json</code> but we still can’t build because we don’t have a builder. If we tried to run the workspace build right now it would not be happy.</p>

<p>However, you should notice that Liferay Workspace recognises the fact that there is a JavaScript build in the module and does some setup and attempts to execute some npm tasks. However it errors out with something along the lines of:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> Task :modules:adding-javascript-build:downloadLiferayModuleConfigGenerator

...

<span class="o">&gt;</span> core-js@2.6.12 postinstall /home/rotty/projects/com.github.rotty3000.workspace/modules/adding-javascript-build/node_modules/core-js
<span class="o">&gt;</span> node <span class="nt">-e</span> <span class="s2">"try{require('./postinstall')}catch(e){}"</span>

...

npm ERR! core-js@2.6.12 postinstall: <span class="sb">`</span>node <span class="nt">-e</span> <span class="s2">"try{require('./postinstall')}catch(e){}"</span><span class="sb">`</span>
npm ERR! Exit status 139
npm ERR! 
npm ERR! Failed at the core-js@2.6.12 postinstall script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A <span class="nb">complete </span>log of this run can be found <span class="k">in</span>:
npm ERR!     /home/rotty/.npm/_logs/2021-03-22T03_53_00_117Z-debug.log

<span class="o">&gt;</span> Task :modules:adding-javascript-build:downloadLiferayModuleConfigGenerator FAILED

...

BUILD FAILED <span class="k">in </span>31s
4 actionable tasks: 4 executed
</code></pre></div></div>

<p>Don’t even bother trying to make sense of this, it’s really just a long winded way of saying</p>

<blockquote>
  <p>I really have no idea what’s going on… Please help!!!</p>
</blockquote>

<p>Liferay Workspace is trying to use it’s built in NPM builder plugin configuration but is failing to find what it thinks are critical pieces of information to tell it what to do. To get around this we need to add a <code class="language-plaintext highlighter-rouge">build</code> script defined in <code class="language-plaintext highlighter-rouge">package.json</code> that will perform an action of our choosing. Let’s start with something very trivial:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@@ -4,7 +4,8 @@</span>
 	"description": "",
 	"main": "index.js",
 	"scripts": {
<span class="gd">-		"test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
</span><span class="gi">+		"test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
+		"build": "echo 'test, test, test'"
</span> 	},
 	"keywords": [],
 	"author": "",
</code></pre></div></div>

<p>If you rebuild now you should see that Liferay Workspace accepted that we’re taking over and ran our simple <code class="language-plaintext highlighter-rouge">build</code> script. This means we’re now in control. Great!</p>

<p>Since we’re creating a Liferay module we surely never intend to publish this project to an NPM repository so we should declare the package <code class="language-plaintext highlighter-rouge">private</code> and we also don’t need an entry point per say so let’s make both edits next:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@@ -2,7 +2,7 @@</span>
 	"name": "adding-javascript-build",
 	"version": "1.0.0",
 	"description": "",
<span class="gd">-	"main": "index.js",
</span><span class="gi">+	"private": true,
</span> 	"scripts": {
 		"test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
 		"build": "echo 'test, test, test'"
</code></pre></div></div>

<h3 id="npmnode-version">NPM/Node Version</h3>

<p>When working with the most recent tooling we often run into limitations if using older versions of NPM and/or Node. Liferay’s conservative defaults are a little too dated to work with the state of the art in JavaScript. Let’s boost that up but not too high. We want to ensure the best compatibility in case the workspace contains older projects. I’ve found that the sweet spot is around version <code class="language-plaintext highlighter-rouge">12.20.0</code> of Node. Most order JavaScript projects only need slight alterations to work with this version and we also are able to execute almost all the latest JavaScript tooling.</p>

<p>Open the <code class="language-plaintext highlighter-rouge">build.gradle</code> file in the root of the Liferay Workspace and add the following (if you have existing build configurations just make sure to integrate this):</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">allprojects</span> <span class="o">{</span>
	<span class="n">plugins</span><span class="o">.</span><span class="na">withId</span><span class="o">(</span><span class="s2">"com.liferay.node"</span><span class="o">)</span> <span class="o">{</span>
		<span class="n">node</span><span class="o">.</span><span class="na">global</span> <span class="o">=</span> <span class="kc">true</span>
		<span class="n">node</span><span class="o">.</span><span class="na">nodeVersion</span> <span class="o">=</span> <span class="s1">'12.21.0'</span>
		<span class="n">node</span><span class="o">.</span><span class="na">npmVersion</span> <span class="o">=</span> <span class="s1">'6.14.11'</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="typescript"><a href="https://www.typescriptlang.org/">TypeScript</a></h3>

<p>In this specific scenario we have TypeScript source files which means we need to compile them so we need the tools to handle that:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install </span>typescript <span class="nt">--save-dev</span>
</code></pre></div></div>

<p>We also need to configure some details of TypeScript compilation. For this we need a configuration file called <a href="https://www.typescriptlang.org/tsconfig"><code class="language-plaintext highlighter-rouge">tsconfig.json</code></a>. Create this file in the module directory with the following contents:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
	</span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
		</span><span class="nl">"allowJs"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
		</span><span class="nl">"checkJs"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
		</span><span class="nl">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"es6"</span><span class="p">,</span><span class="w">
		</span><span class="nl">"moduleResolution"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node"</span><span class="p">,</span><span class="w">
		</span><span class="nl">"noImplicitAny"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
		</span><span class="nl">"outDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./build/resources/main/META-INF/resources/"</span><span class="p">,</span><span class="w">
		</span><span class="nl">"rootDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./src/main/resources/META-INF/resources/"</span><span class="p">,</span><span class="w">
		</span><span class="nl">"sourceMap"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
		</span><span class="nl">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
		</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"es6"</span><span class="w">
	</span><span class="p">},</span><span class="w">
	</span><span class="nl">"include"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
		</span><span class="s2">"./src/main/resources/META-INF/resources/**/*"</span><span class="w">
	</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Check the documentation for the specific details of each configuration but this is approximately what is needed for our use case, where we have a <em>Maven-ish</em> project structure which produces a <em>skinny WAB</em> result so our configuration reflects that.</p>

<p>You can test the output by executing the TypeScript compiler command directly from the command line:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tsc
</code></pre></div></div>

<p>If you have any errors whether logical, or with type safety you should find out pretty quickly.</p>

<p>We could stop here if all we wanted was to compile our TypeScript sources into JavaScript. We could modify the <code class="language-plaintext highlighter-rouge">build</code> script to invoke the TypeScript compiler we’d be off to the races.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 	"private": true,
 	"scripts": {
 		"test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
<span class="gd">-		"build": "echo 'test, test, test'"
</span><span class="gi">+		"build": "tsc"
</span> 	},
 	"keywords": [],
 	"author": "",

</code></pre></div></div>

<p>The resulting output of executing the gradle <code class="language-plaintext highlighter-rouge">jar</code> task is now:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">]</span><span class="nv">$ </span>bnd print <span class="nt">-l</span> build/libs/adding.javascript.build-1.0.0.jar 
<span class="o">[</span>LIST]
META-INF
  MANIFEST.MF
META-INF/resources
META-INF/resources &lt;no contents&gt;
META-INF/resources/js
  index.js
  index.js.map
  index.ts
OSGI-INF
  adding.javascript.build.AddingJavascriptBuild.xml
adding
adding &lt;no contents&gt;
adding/javascript
adding/javascript &lt;no contents&gt;
adding/javascript/build
  AddingJavascriptBuild.class
</code></pre></div></div>

<p>But we’re not done yet so let’s move ahead.</p>

<h3 id="webpack"><a href="https://webpack.js.org">Webpack</a></h3>

<p>.. is “<a href="1">a <em>static module bundler</em> for modern JavaScript applications.</a>”</p>

<p>We want Webpack to handle the complexity of <em>bundling</em> (a.k.a. assembling) any dependencies we might introduce. We’ll install it by executing:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install </span>webpack webpack-cli <span class="nt">--save-dev</span>
</code></pre></div></div>

<p>While Webpack can function without configuration, in our case we have loftier goals than what the basic configuration supports. So let’s start with a relatively simple <code class="language-plaintext highlighter-rouge">webpack.config.js</code> file. Keep in mind we also want to handle the fact that we have TypeScript:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">PUBLIC_PATH</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/o/adding-javascript-build/</span><span class="dl">'</span><span class="p">;</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
	<span class="na">mode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">production</span><span class="dl">'</span><span class="p">,</span>
	<span class="na">context</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">),</span>
	<span class="na">entry</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./src/main/resources/META-INF/resources/js</span><span class="dl">'</span><span class="p">,</span>
	<span class="na">module</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">rules</span><span class="p">:</span> <span class="p">[</span>
			<span class="p">{</span>
				<span class="na">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">\.</span><span class="sr">tsx</span><span class="se">?</span><span class="sr">$/</span><span class="p">,</span>
				<span class="na">use</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ts-loader</span><span class="dl">'</span><span class="p">,</span>
				<span class="na">exclude</span><span class="p">:</span> <span class="sr">/node_modules/</span><span class="p">,</span>
			<span class="p">},</span>
		<span class="p">],</span>
	<span class="p">},</span>
	<span class="na">resolve</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">extensions</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">.tsx</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.ts</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.js</span><span class="dl">'</span><span class="p">],</span>
	<span class="p">},</span>
	<span class="na">output</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">js/bundle.js</span><span class="dl">'</span><span class="p">,</span>
		<span class="na">libraryTarget</span><span class="p">:</span> <span class="dl">'</span><span class="s1">window</span><span class="dl">'</span><span class="p">,</span>
		<span class="na">path</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="dl">'</span><span class="s1">./build/resources/main/META-INF/resources/</span><span class="dl">'</span><span class="p">),</span>
		<span class="na">publicPath</span><span class="p">:</span> <span class="nx">PUBLIC_PATH</span><span class="p">,</span>
	<span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Several key points to look at here. First thing we notice is that in order to handle the TypeScript files we <em>use</em> a plugin called <code class="language-plaintext highlighter-rouge">ts-loader</code> . We need to install that:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install </span>ts-loader <span class="nt">--save-dev</span>
</code></pre></div></div>

<p>The rest of the configuration revolves around where our sources are, and where we want the output to end up. There are also places to configure details about the output module syntax and whether the result is optimised or production and so on. I recommend reading slowly through the webpack configuration documentation to learn the key concepts.</p>

<p>With this we can also change the <code class="language-plaintext highlighter-rouge">build</code> script to call Webpack, which along with the other Webpack related  changes makes for the following:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@@ -5,12 +5,15 @@</span>
 	"private": true,
 	"scripts": {
 		"test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
<span class="gd">-		"build": "tsc"
</span><span class="gi">+		"build": "webpack"
</span> 	},
 	"keywords": [],
 	"author": "",
 	"license": "ISC",
 	"devDependencies": {
<span class="gd">-		"typescript": "^4.2.3"
</span><span class="gi">+		"ts-loader": "^8.0.18",
+		"typescript": "^4.2.3",
+		"webpack": "^5.27.2",
+		"webpack-cli": "^4.5.0"
</span> 	}
 }
</code></pre></div></div>

<p>If we run our build again we should now have a jar that contains the following:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">]</span><span class="nv">$ </span>bnd print <span class="nt">-l</span> build/libs/adding.javascript.build-1.0.0.jar 
<span class="o">[</span>LIST]
META-INF
  MANIFEST.MF
META-INF/resources
META-INF/resources &lt;no contents&gt;
META-INF/resources/js
  bundle.js
  index.ts
OSGI-INF
  adding.javascript.build.AddingJavascriptBuild.xml
adding
adding &lt;no contents&gt;
adding/javascript
adding/javascript &lt;no contents&gt;
adding/javascript/build
  AddingJavascriptBuild.class
</code></pre></div></div>

<p>And finally, if we want to load our JavaScript file in the portal we can do so by adding the following in the <code class="language-plaintext highlighter-rouge">bnd.bnd</code> file:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Bundle-SymbolicName: adding.javascript.build
 Bundle-Version: 1.0.0
 
<span class="gi">+Liferay-JS-Resources-Top-Head: /js/bundle.js?ts=${tstamp}
+Liferay-Top-Head-Weight: 1000
</span> Web-ContextPath: /adding-javascript-build
</code></pre></div></div>

<p>Couple of points to mention here. First is that we added a build-time auto-generated <code class="language-plaintext highlighter-rouge">ts</code> parameter to the URL. That will help flush client caches whenever we redeploy a new version of our WAB. Besides that, the URL will be prefixed by the full context path of the WAB at runtime for us. This could have been achieved with file name hashing via Webpack, but for my own reasons I wanted to avoid doing that. Maybe a point for future discussion…</p>

<p>The second point is about the header  <code class="language-plaintext highlighter-rouge">Liferay-Top-Head-Weight</code>. This allows us to give the file a weight among all other <em>top head</em> JavaScript files in the portal so you have some measure of control on ordering. In this case the larger the number the later it gets added. Values range from <code class="language-plaintext highlighter-rouge">Integer.MIN_VALUE</code> to <code class="language-plaintext highlighter-rouge">Integer.MAX_VALUE</code>.</p>

<h3 id="conclusion">Conclusion</h3>

<p>The ability to add JavaScript builds in your Liferay Workspace modules is pretty essential these days. As it turns out once I waded through the mounds of information and grasped some of the concepts it turned out not to be so bad. In later posts I’ll dig into some more advanced scenarios and use cases.</p>

<p>Until next time.</p>]]></content><author><name>Raymond Augé</name></author><summary type="html"><![CDATA[Adding non-trivial JavaScript to a Liferay Workspace module project has always caused me some degree of pain. This post demonstrates how I accomplish this these days.]]></summary></entry><entry><title type="html">Windows/Git Bash/Curl/Corporate Certificate</title><link href="https://rotty3000.doublebite.com/Windows-Git-Bash-Curl-Certificate/" rel="alternate" type="text/html" title="Windows/Git Bash/Curl/Corporate Certificate" /><published>2021-01-08T00:00:00+00:00</published><updated>2021-01-08T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/Windows-Git-Bash-Curl-Certificate</id><content type="html" xml:base="https://rotty3000.doublebite.com/Windows-Git-Bash-Curl-Certificate/"><![CDATA[<p>Recently I had the need to use Windows… where I had to use Git. Thankfully there is a product called <a href="https://gitforwindows.org">Git Bash</a> which saved me.</p>

<p>In the meantime I had suddenly the need to make some curl requests to HTTPS endpoints AND I’m on a machine which has a corporate security proxy. Nice thing is that Git Bash also includes curl (among several other necessary commands.)</p>

<p>But when I tried to use it I saw errors like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>foo@bar MINGW64 ~
<span class="nv">$ </span>curl https://cdn.azul.com/zulu/bin/zulu11.43.55-ca-jdk11.0.9.1-win_x64.zip <span class="nt">--output</span> zulu11.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 <span class="nt">--</span>:--:-- <span class="nt">--</span>:--:-- <span class="nt">--</span>:--:--     0
curl: <span class="o">(</span>60<span class="o">)</span> SSL certificate problem: unable to get <span class="nb">local </span>issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
</code></pre></div></div>

<p>Seemed in order to get through the proxy a corporate certificate is required.</p>

<p>Now, at that moment I could not invoke admin rights to place the certificate into the Git Bash <code class="language-plaintext highlighter-rouge">mingw</code> install directory as instructed, so how to proceed?</p>

<p>You’ll notice that later in the <a href="https://curl.haxx.se/docs/sslcerts.html">documentation</a> it describes that curl has a environment variable <code class="language-plaintext highlighter-rouge">CURL_CA_BUNDLE</code> that does not require admin rights to modification of the file system.</p>

<p>Edit your <code class="language-plaintext highlighter-rouge">~/.bashrc</code> file and add the following:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">CURL_CA_BUNDLE</span><span class="o">=</span><span class="s2">"/c/foo/corp-cert.crt"</span>
</code></pre></div></div>

<p>With that curl commands should start the work.</p>]]></content><author><name>Raymond Augé</name></author><summary type="html"><![CDATA[How to configure Git Bash CURL command to use corporate certificat without admin]]></summary></entry><entry><title type="html">Introspect Configuration Admin from Gogo Shell</title><link href="https://rotty3000.doublebite.com/Introspect-Configuration-Admin-from-Gogo-Shell/" rel="alternate" type="text/html" title="Introspect Configuration Admin from Gogo Shell" /><published>2020-11-18T00:00:00+00:00</published><updated>2020-11-18T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/Introspect-Configuration-Admin-from-Gogo-Shell</id><content type="html" xml:base="https://rotty3000.doublebite.com/Introspect-Configuration-Admin-from-Gogo-Shell/"><![CDATA[<p><a href="https://felix.apache.org/documentation/subprojects/apache-felix-gogo.html">Apache Felix Gogo</a> is by far one of the most potent tools in any OSGi development toolkit. And if you are fortunate enough to have access to a very modern version of <a href="https://github.com/apache/felix-dev/tree/master/configadmin">Apache Felix Configuration Admin</a> you can already benefit from the fact that you can interact with Configuration Admin through the Gogo shell with the commands:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">cm:createfactoryconfiguration</code> - create a factory configuration (maps to the Configuration Admin method <a href="https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/cm/ConfigurationAdmin.html#createFactoryConfiguration-java.lang.String-"><code class="language-plaintext highlighter-rouge">createFactoryConfiguration(String)</code></a> or <a href="https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/cm/ConfigurationAdmin.html#createFactoryConfiguration-java.lang.String-java.lang.String-"><code class="language-plaintext highlighter-rouge">createFactoryConfiguration(String,String)</code></a>)</li>
  <li><code class="language-plaintext highlighter-rouge">cm:getconfiguration</code> - create a single configuration (maps to the Configuration Admin method <a href="https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/cm/ConfigurationAdmin.html#getConfiguration-java.lang.String-"><code class="language-plaintext highlighter-rouge">getConfiguration(String)</code></a> or <a href="https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/cm/ConfigurationAdmin.html#getConfiguration-java.lang.String-java.lang.String-"><code class="language-plaintext highlighter-rouge">getConfiguration(String,String)</code></a>)</li>
  <li><code class="language-plaintext highlighter-rouge">cm:getfactoryconfiguration</code> - get or create a factory configuration (maps to the Configuration Admin method <a href="https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/cm/ConfigurationAdmin.html#getFactoryConfiguration-java.lang.String-java.lang.String-"><code class="language-plaintext highlighter-rouge">getFactoryConfiguration(String,String)</code></a> or <a href="https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/cm/ConfigurationAdmin.html#getFactoryConfiguration-java.lang.String-java.lang.String-java.lang.String-"><code class="language-plaintext highlighter-rouge">getFactoryConfiguration(String,String,String)</code></a>)</li>
  <li><code class="language-plaintext highlighter-rouge">cm:listconfigurations</code> - list configurations (maps to the Configuration Admin method <a href="https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/cm/ConfigurationAdmin.html#listConfigurations-java.lang.String-"><code class="language-plaintext highlighter-rouge">listConfigurations(String)</code></a>)</li>
</ul>

<p><strong><em>Note:</em></strong> <em>You may find that when using <code class="language-plaintext highlighter-rouge">help</code> the commands come up in camel case. Luckily commands are case insensitive so you can use all lower case to simplify execution if you choose.</em></p>

<h3 id="listconfigurations">listconfigurations</h3>

<p>The most useful function right off the bat is to list any existing configurations that might already exist in the framework using the <code class="language-plaintext highlighter-rouge">listconfigurations</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! listconfigurations <span class="s2">"(service.pid=*)"</span>
Configuration <span class="nv">PID</span><span class="o">=</span>foo.bar, <span class="nv">factoryPID</span><span class="o">=</span>null, <span class="nv">bundleLocation</span><span class="o">=</span>?
Configuration <span class="nv">PID</span><span class="o">=</span>foo.baz, <span class="nv">factoryPID</span><span class="o">=</span>null, <span class="nv">bundleLocation</span><span class="o">=</span>?
</code></pre></div></div>

<p>To get all configurations:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! listconfigurations null
...
Configuration <span class="nv">PID</span><span class="o">=</span>foo.bar, <span class="nv">factoryPID</span><span class="o">=</span>null, <span class="nv">bundleLocation</span><span class="o">=</span>?
Configuration <span class="nv">PID</span><span class="o">=</span>foo.baz, <span class="nv">factoryPID</span><span class="o">=</span>null, <span class="nv">bundleLocation</span><span class="o">=</span>?
...
</code></pre></div></div>

<p>To get an individual configuration from the list you can use the array syntax:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! c <span class="o">=</span> <span class="o">(</span>listconfigurations <span class="s2">"(service.pid=?)"</span><span class="o">)</span> 0
Properties           <span class="o">[</span><span class="nv">foo</span><span class="o">=</span>bar, service.pid<span class="o">=</span>foo.bar]
Attributes           <span class="o">[]</span>
FactoryPid           null
Pid                  foo.bar
ChangeCount          2
BundleLocation       ?

</code></pre></div></div>

<p>To list the properties of a configuration in tabular form you can use:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! <span class="nv">$c</span> properties
foo                 bar
service.pid         foo.bar

</code></pre></div></div>

<h3 id="getconfiguration-get-or-create-single-configurations">getconfiguration: Get or create single configurations</h3>

<p>You can get a configuration using:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! c <span class="o">=</span> getconfiguration <span class="s2">"foo.biz"</span>
Properties           null
Attributes           <span class="o">[]</span>
FactoryPid           null
Pid                  foo.biz
ChangeCount          1
BundleLocation       reference:file:???????/org.apache.felix.gogo.runtime-1.1.4.jar

</code></pre></div></div>

<p><strong>However</strong>, you’ll note that if the configuration doesn’t exist a new instance is returned. The problem is that if you use the single argument <code class="language-plaintext highlighter-rouge">getconfiguration</code> AND the configuration does not already exist what you will get is an instance that is only visible to the bundle that triggered the creation (notice the <code class="language-plaintext highlighter-rouge">BundleLocation</code> above), which is not usually what you want. In that case you should use:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! c <span class="o">=</span> getconfiguration <span class="s2">"foo.bom"</span> <span class="s2">"?"</span>
Properties           null
Attributes           <span class="o">[]</span>
FactoryPid           null
Pid                  foo.bom
ChangeCount          1
BundleLocation       ?

</code></pre></div></div>

<p><strong>Note</strong> that now you have a <code class="language-plaintext highlighter-rouge">BundleLocation</code> of <code class="language-plaintext highlighter-rouge">?</code> which means it is visible to any bundles that want to read it.</p>

<p>If you don’t want the <em>or create</em> behaviour it’s best to use <code class="language-plaintext highlighter-rouge">listconfigurations</code> when searching for configurations (it’s more powerful anyway since you can filter on any properties of the configurations).</p>

<h3 id="configuration-plugins--processed-properties">Configuration Plugins &amp; Processed Properties</h3>

<p>If you happen to have a Configuration Admin that implements the 1.6 version of the spec you might also have <a href="https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.cm.html#i1459884">Configuration Plugins</a> that are able to alter or interpolate configurations (e.g. <a href="https://github.com/apache/felix-dev/tree/master/configadmin-plugins/interpolation">Felix ConfigAdmin Interpolation Plugin</a>.)</p>

<p>Suppose you have this FileInstall config called <code class="language-plaintext highlighter-rouge">game.pid.config</code> which uses Felix Interpolation Plugin interpolation:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">player.initial.lives</span><span class="p">=</span><span class="s">"$[env:player_initial_lives]"</span>
<span class="py">player.maximum-lives</span><span class="p">=</span><span class="s">"5"</span>
</code></pre></div></div>

<p>You’d want to call the <code class="language-plaintext highlighter-rouge">getProcessedProperties</code> method of <code class="language-plaintext highlighter-rouge">Configuration</code> to cause plugins to execute over the properties. This method does have one argument, a <code class="language-plaintext highlighter-rouge">ServiceReference</code> to a service that can contextualize the processing (i.e. to find properties/files for instance.)</p>

<p>In the case of the Felix Interpolation Plugin all you need is a <code class="language-plaintext highlighter-rouge">ServiceReference</code> to the <code class="language-plaintext highlighter-rouge">ConfigurationAdmin</code> service itself:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! sr <span class="o">=</span> servicereference org.osgi.service.cm.ConfigurationAdmin
</code></pre></div></div>

<p>With that you can call the <code class="language-plaintext highlighter-rouge">getProcessedProperties</code> method to see the properties fully resolved:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! <span class="o">(</span>getconfiguration <span class="s2">"game.pid"</span><span class="o">)</span> processedproperties <span class="nv">$sr</span>
player.initial.lives 3
player.maximum-lives 5
</code></pre></div></div>

<h3 id="create-configurations-from-scratch">Create configurations from scratch</h3>

<p>So let’s say that no configuration exist for the PID and you also want to configure it with properties. First you need a way to create a <code class="language-plaintext highlighter-rouge">Dictionary</code> object since that is the argument required by the <code class="language-plaintext highlighter-rouge">Configuration.update(Dictionary)</code> method.</p>

<p>You can use the following to create a dictionary object:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! d <span class="o">=</span> <span class="o">(((</span>bundle 0<span class="o">)</span> loadclass java.util.Hashtable<span class="o">)</span> newInstance<span class="o">)</span>

</code></pre></div></div>
<p>Add some properties:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! <span class="nv">$d</span> put <span class="s2">"Foo"</span> <span class="s2">"bar"</span>
</code></pre></div></div>
<p>Print the result:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! <span class="nv">$d</span>
Foo                 bar

</code></pre></div></div>

<p>Now you can pass this dictionary to the configuration you have stored in the var <code class="language-plaintext highlighter-rouge">c</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! <span class="nv">$c</span> update <span class="nv">$d</span>
g! <span class="nv">$c</span>
Properties           <span class="o">[</span><span class="nv">Foo</span><span class="o">=</span>bar, service.pid<span class="o">=</span>foo.bom]
Attributes           <span class="o">[]</span>
FactoryPid           null
Pid                  foo.bom
ChangeCount          2
BundleLocation       ?

</code></pre></div></div>

<h3 id="createfactoryconfiguration-create-factory-configurations">createfactoryconfiguration: Create factory configurations</h3>

<p>The basic incarnation of <code class="language-plaintext highlighter-rouge">createfactoryconfiguration</code> has the same gotchas as <code class="language-plaintext highlighter-rouge">getconfiguration</code> where it will create a configuration with a <code class="language-plaintext highlighter-rouge">BundleLocation</code> limited to the caller bundle. So you should prefer the two argument version as follows:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! c <span class="o">=</span> createfactoryconfiguration <span class="s2">"foo.biz"</span> <span class="s2">"?"</span>
Properties           null
Attributes           <span class="o">[]</span>
FactoryPid           foo.biz
Pid                  foo.biz.54bf0c1f-a7bd-4bfd-a80f-fedf58793442
ChangeCount          1
BundleLocation       ?

</code></pre></div></div>

<p>Notice that now the PID contains a generated part, e.g. <code class="language-plaintext highlighter-rouge">foo.biz.54bf0c1f-a7bd-4bfd-a80f-fedf58793442</code>.</p>

<p>Setting configuration properties is exactly the same as above.</p>

<h3 id="getfactoryconfiguration-named-factory-configurations">getfactoryconfiguration: Named factory configurations</h3>

<p>Recent versions of Configuration Admin specification make it possible to create <em>named</em> configurations. The command <code class="language-plaintext highlighter-rouge">getfactoryconfiguration</code> follows the pattern <code class="language-plaintext highlighter-rouge">getConfiguration</code> with it’s <em>get or create</em> paradigm. However, the difference between this and the <code class="language-plaintext highlighter-rouge">createfactoryconfiguration</code> is the <em>name</em> argument which allows passing a human readable string to uniquely disambiguate factory configurations beyond the opaque generated suffix normally used for factory configurations which make them hard to identify. Here we also prefer adding the third argument so that we make the configuration readable by any bundle.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! c <span class="o">=</span> getfactoryconfiguration <span class="s2">"foo.biz"</span> <span class="s2">"one"</span> <span class="s2">"?"</span>
Properties           null
Attributes           <span class="o">[]</span>
FactoryPid           foo.biz
Pid                  foo.biz~one
ChangeCount          1
BundleLocation       ?

</code></pre></div></div>

<p>Notice that in this case the PID is suffixed with a tilde (<code class="language-plaintext highlighter-rouge">~</code>) followed by the human readable string argument we passed (<code class="language-plaintext highlighter-rouge">one</code>).</p>

<h3 id="what-about-older-versions-of-felix-configuration-admin">What about older versions of Felix Configuration Admin</h3>

<p>Earlier I mentioned:</p>

<blockquote>
  <p>if you are fortunate enough to have access to a very modern version of <a href="https://github.com/apache/felix-dev/tree/master/configadmin">Apache Felix Configuration Admin</a> you can already benefit from the fact that you can interact with Configuration Admin through the Gogo shell with the commands</p>
</blockquote>

<p>Well, it turns out that even if you are using an older version you can still do most of the above provided you create a command from the Configuration Admin service (if the service is available).</p>

<p>First you may need to setup the command context so that you have a few basic commands:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! addcommand context <span class="k">${</span><span class="p">.context</span><span class="k">}</span>
</code></pre></div></div>
<p><em>(This gives you access to commands like <code class="language-plaintext highlighter-rouge">servicereference &lt;fqcn|filter&gt;</code>.)</em></p>

<p>Now you can add the config admin service commands with:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g! addcommand cm <span class="o">(</span>service <span class="o">(</span>servicereference <span class="s2">"org.osgi.service.cm.ConfigurationAdmin"</span><span class="o">))</span>
</code></pre></div></div>

<p>That’s it! Now everything above should work exactly as documented.</p>]]></content><author><name>Raymond Augé</name></author><summary type="html"><![CDATA[Apache Felix Gogo is by far one of the most potent tools in any OSGi development toolkit. And if you are fortunate enough to have access to a very modern version of Apache Felix Configuration Admin you can already benefit from the fact that you can interact with Configuration Admin through the gogo shell with the commands.]]></summary></entry><entry><title type="html">Dynamic Eclipse Target Platform</title><link href="https://rotty3000.doublebite.com/Dynamic-Eclipse-Target-Platform/" rel="alternate" type="text/html" title="Dynamic Eclipse Target Platform" /><published>2020-05-11T00:00:00+00:00</published><updated>2020-05-11T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/Dynamic-Eclipse-Target-Platform</id><content type="html" xml:base="https://rotty3000.doublebite.com/Dynamic-Eclipse-Target-Platform/"><![CDATA[<p>I’ve been one of the maintainers of the Eclipse Equinox Http Servlet bundles for several years now. I don’t work on many Equinox implementations so this task is not part of my daily grind and I quite often forget how to get back into it when needed.</p>

<p>The Eclipse Platform is now releasing quite frequently and this is awesome in many respects, but one side effect is that the configuration in Eclipse needed to work against the HEAD of master is constantly changing and managing this, or figuring out what is required to get things working in the IDE again can be a pain.</p>

<p>By far the most annoying detail is the Target Platform and specifically a couple of Orbit bundles I need for equinox http servlet. The issue is caused by the version of the Orbit drop and Platform SDK always changing for obvious reasons.</p>

<p>The solution however came in the form of a tutorial on <a href="https://www.vogella.com/">vogella.com</a> called <a href="https://www.vogella.com/tutorials/EclipseTargetPlatform/article.html">Eclipse Target Platform</a>.</p>

<p>The interesting details are about the dependency versions. Apparently in editing the <code class="language-plaintext highlighter-rouge">.target</code> resource you can use the placeholder version <code class="language-plaintext highlighter-rouge">0.0.0</code> to indicate using the latest.</p>

<p>The other interesting detail is that the 2 P2 sites I care about for this target platform both have <em>latest</em> aliases:</p>

<ul>
  <li><a href="http://download.eclipse.org/releases/latest">http://download.eclipse.org/releases/latest</a></li>
  <li><a href="https://download.eclipse.org/tools/orbit/downloads/latest-R/">https://download.eclipse.org/tools/orbit/downloads/latest-R/</a></li>
</ul>

<p>I don’t know about you but I find these aliases to be notoriously hard to find.</p>

<p>So now I have an eclipse project in my workspace called <code class="language-plaintext highlighter-rouge">target-platform</code> like in the <em>Vogella</em> tutorial and a <code class="language-plaintext highlighter-rouge">latest-target-platform.target</code> file which contains:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;</span>
<span class="cp">&lt;?pde version="3.8"?&gt;</span>
<span class="nt">&lt;target</span> <span class="na">name=</span><span class="s">"latest-target-platform"</span><span class="nt">&gt;</span>
	<span class="nt">&lt;locations&gt;</span>
		<span class="nt">&lt;location</span> <span class="na">includeAllPlatforms=</span><span class="s">"false"</span> <span class="na">includeConfigurePhase=</span><span class="s">"true"</span> <span class="na">includeMode=</span><span class="s">"planner"</span> <span class="na">includeSource=</span><span class="s">"true"</span> <span class="na">type=</span><span class="s">"InstallableUnit"</span><span class="nt">&gt;</span>
			<span class="nt">&lt;repository</span> <span class="na">location=</span><span class="s">"http://download.eclipse.org/releases/latest"</span><span class="nt">/&gt;</span>
			<span class="nt">&lt;unit</span> <span class="na">id=</span><span class="s">"org.eclipse.sdk.feature.group"</span> <span class="na">version=</span><span class="s">"0.0.0"</span><span class="nt">/&gt;</span>
		<span class="nt">&lt;/location&gt;</span>
		<span class="nt">&lt;location</span> <span class="na">includeAllPlatforms=</span><span class="s">"false"</span> <span class="na">includeConfigurePhase=</span><span class="s">"true"</span> <span class="na">includeMode=</span><span class="s">"planner"</span> <span class="na">includeSource=</span><span class="s">"true"</span> <span class="na">type=</span><span class="s">"InstallableUnit"</span><span class="nt">&gt;</span>
			<span class="nt">&lt;repository</span> <span class="na">location=</span><span class="s">"https://download.eclipse.org/tools/orbit/downloads/latest-R/"</span><span class="nt">/&gt;</span>
			<span class="nt">&lt;unit</span> <span class="na">id=</span><span class="s">"org.apache.commons.fileupload"</span> <span class="na">version=</span><span class="s">"0.0.0"</span><span class="nt">/&gt;</span>
			<span class="nt">&lt;unit</span> <span class="na">id=</span><span class="s">"org.apache.commons.fileupload.source"</span> <span class="na">version=</span><span class="s">"0.0.0"</span><span class="nt">/&gt;</span>
		<span class="nt">&lt;/location&gt;</span>
	<span class="nt">&lt;/locations&gt;</span>
<span class="nt">&lt;/target&gt;</span>
</code></pre></div></div>

<p>Now I hope I never have to search for the correct Orbit and Eclispe SDKs ever again.</p>]]></content><author><name>Raymond Augé</name></author><summary type="html"><![CDATA[I've been one of the maintainers of the Eclipse Equinox Http Servlet bundles for several years now.]]></summary></entry><entry><title type="html">Gradle Integration Test - Starting an external process</title><link href="https://rotty3000.doublebite.com/Integration-Test-With-External-Process-In-Gradle/" rel="alternate" type="text/html" title="Gradle Integration Test - Starting an external process" /><published>2020-05-11T00:00:00+00:00</published><updated>2020-05-11T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/Integration-Test-With-External-Process-In-Gradle</id><content type="html" xml:base="https://rotty3000.doublebite.com/Integration-Test-With-External-Process-In-Gradle/"><![CDATA[<p>Recently we worked on a gradle project where we wanted to run integration tests for a soap client. This meant starting an external process providing the soap endpoint. Luckily the folks over at <a href="https://smartbear.com/">smartbear.com</a> provide a nice open source tool for running mocked soap services called <a href="https://www.soapui.org/">Soap UI</a>.</p>

<p>Getting a mocked service up and running was pretty trivial thanks to some pretty good documentation. I won’t go into any details because I could never do their documentation justice in a short post. But creating a mock service with responses and some scripts was pretty simple.</p>

<p>Once we had a mock service built our main concern was running it in a CI environment using gradle and wrapped around the soap client test cases.</p>

<p>Executing the mock service standalone involves executing a command like the following:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sh SoapUI/bin/mockservicerunner.sh <span class="nt">-s</span> src/testIntegration/resources/soapui-settings.xml src/testIntegration/resources/soapui-project.xml
</code></pre></div></div>

<p>This starts a process which runs the mocked soap service until any input is sent to stdin which terminates it.</p>

<p>Coordinating this in gradle involves adding some actions before and after the integration tests run.</p>

<p>Note that we added the SoapUI binaries directly into source control so that they would be available to execute during the build.</p>

<p>The first thing we’re going to do is create a variable to hold the external process so that we can stop it when we’re done:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">def</span> <span class="n">process</span>
</code></pre></div></div>

<p>In this build integration tests are defined and executed by the <code class="language-plaintext highlighter-rouge">testIntegration</code> task so adding an action before execution involves configuring the <code class="language-plaintext highlighter-rouge">doFirst</code> closure for it.</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tasks</span><span class="o">.</span><span class="na">named</span><span class="o">(</span><span class="s1">'testIntegration'</span><span class="o">)</span> <span class="o">{</span>
	<span class="n">doFirst</span> <span class="o">{</span>
		<span class="kt">def</span> <span class="n">cmd</span> <span class="o">=</span> <span class="s2">"sh SoapUI/bin/mockservicerunner.sh -s src/testIntegration/resources/soapui-settings.xml src/testIntegration/resources/soapui-project.xml"</span>

		<span class="n">process</span> <span class="o">=</span> <span class="n">cmd</span><span class="o">.</span><span class="na">execute</span><span class="o">([],</span> <span class="n">project</span><span class="o">.</span><span class="na">projectDir</span><span class="o">)</span>
		<span class="n">process</span><span class="o">.</span><span class="na">consumeProcessOutput</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">,</span> <span class="n">System</span><span class="o">.</span><span class="na">err</span><span class="o">)</span>
		<span class="c1">// give process some time to fully start</span>
		<span class="n">sleep</span><span class="o">(</span><span class="mi">2000</span><span class="o">)</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>We used the groovy <code class="language-plaintext highlighter-rouge">execute</code> function and passed two parameters; the environment (<code class="language-plaintext highlighter-rouge">[]</code>) and the current working directory (<code class="language-plaintext highlighter-rouge">project.projectDir</code>) so that relative file paths would be relative to the project.</p>

<p>In order to see that the process is started correctly we also made sure groovy consumes all the output from the process and adds it to the gradle output using the <code class="language-plaintext highlighter-rouge">consumeProcessOutput</code> helper method.</p>

<p>We also need to shutdown the process when tests are done. So we created a <code class="language-plaintext highlighter-rouge">stopSoapUi</code> task with a <code class="language-plaintext highlighter-rouge">doLast</code> action:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tasks</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="s1">'stopSoapUI'</span><span class="o">)</span> <span class="o">{</span>
	<span class="n">doLast</span> <span class="o">{</span>
		<span class="n">println</span><span class="o">(</span><span class="s1">'Stopping SoapUI'</span><span class="o">)</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">process</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="n">process</span><span class="o">.</span><span class="na">withWriter</span> <span class="o">{</span><span class="n">it</span> <span class="o">&lt;&lt;</span> <span class="s1">' '</span><span class="o">}</span>
			<span class="n">process</span><span class="o">.</span><span class="na">waitForOrKill</span><span class="o">(</span><span class="mi">1000</span><span class="o">)</span>
			<span class="n">process</span> <span class="o">=</span> <span class="kc">null</span>
		<span class="o">}</span>
	<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>In the <code class="language-plaintext highlighter-rouge">stopSoapUI</code> task we send a character to the stdin of the process to ask it to terminate properly, then wait for a bit to before we ultimately forcibly kill the process (whichever happens first).</p>

<p>Now that we have our task to stop the process we need to make sure it gets executed regardless of what errors occur either in tests or otherwise. In gradle the way to achieve this type of transactional behaviour is by using the <code class="language-plaintext highlighter-rouge">finalizedBy</code> function. We add a <code class="language-plaintext highlighter-rouge">finalizedBy</code> by to the <code class="language-plaintext highlighter-rouge">testIntegration</code> configuration as follows:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tasks</span><span class="o">.</span><span class="na">named</span><span class="o">(</span><span class="s1">'testIntegration'</span><span class="o">)</span> <span class="o">{</span>
	<span class="n">doFirst</span> <span class="o">{</span>
		<span class="kt">def</span> <span class="n">cmd</span> <span class="o">=</span> <span class="s2">"sh SoapUI/bin/mockservicerunner.sh -s src/testIntegration/resources/soapui-settings.xml src/testIntegration/resources/soapui-project.xml"</span>

		<span class="n">process</span> <span class="o">=</span> <span class="n">cmd</span><span class="o">.</span><span class="na">execute</span><span class="o">([],</span> <span class="n">project</span><span class="o">.</span><span class="na">projectDir</span><span class="o">)</span>
		<span class="n">process</span><span class="o">.</span><span class="na">consumeProcessOutput</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">,</span> <span class="n">System</span><span class="o">.</span><span class="na">err</span><span class="o">)</span>
		<span class="c1">// give process some time to fully start</span>
		<span class="n">sleep</span><span class="o">(</span><span class="mi">2000</span><span class="o">)</span>
	<span class="o">}</span>
	<span class="n">finalizedBy</span> <span class="n">tasks</span><span class="o">.</span><span class="na">named</span><span class="o">(</span><span class="s1">'stopSoapUI'</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div></div>

<p>And there you have it! Using this approach you should be able to handle virtually any external process required for integration testing.</p>

<p>The full code is as follows:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">def</span> <span class="n">process</span>

<span class="n">tasks</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="s1">'stopSoapUI'</span><span class="o">)</span> <span class="o">{</span>
	<span class="n">doLast</span> <span class="o">{</span>
		<span class="n">println</span><span class="o">(</span><span class="s1">'Stopping SoapUI'</span><span class="o">)</span>
		<span class="k">if</span> <span class="o">(</span><span class="n">process</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
			<span class="n">process</span><span class="o">.</span><span class="na">withWriter</span> <span class="o">{</span><span class="n">it</span> <span class="o">&lt;&lt;</span> <span class="s1">' '</span><span class="o">}</span>
			<span class="n">process</span><span class="o">.</span><span class="na">waitForOrKill</span><span class="o">(</span><span class="mi">1000</span><span class="o">)</span>
			<span class="n">process</span> <span class="o">=</span> <span class="kc">null</span>
		<span class="o">}</span>
	<span class="o">}</span>
<span class="o">}</span>

<span class="n">tasks</span><span class="o">.</span><span class="na">named</span><span class="o">(</span><span class="s1">'testIntegration'</span><span class="o">)</span> <span class="o">{</span>
	<span class="n">doFirst</span> <span class="o">{</span>
		<span class="kt">def</span> <span class="n">cmd</span> <span class="o">=</span> <span class="s2">"sh SoapUI/bin/mockservicerunner.sh -s src/testIntegration/resources/soapui-settings.xml src/testIntegration/resources/soapui-project.xml"</span>

		<span class="n">process</span> <span class="o">=</span> <span class="n">cmd</span><span class="o">.</span><span class="na">execute</span><span class="o">([],</span> <span class="n">project</span><span class="o">.</span><span class="na">projectDir</span><span class="o">)</span>
		<span class="n">process</span><span class="o">.</span><span class="na">consumeProcessOutput</span><span class="o">(</span><span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">,</span> <span class="n">System</span><span class="o">.</span><span class="na">err</span><span class="o">)</span>
		<span class="c1">// give process some time to fully start</span>
		<span class="n">sleep</span><span class="o">(</span><span class="mi">2000</span><span class="o">)</span>
	<span class="o">}</span>
	<span class="n">finalizedBy</span> <span class="n">tasks</span><span class="o">.</span><span class="na">named</span><span class="o">(</span><span class="s1">'stopSoapUI'</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div></div>]]></content><author><name>Raymond Augé</name></author><summary type="html"><![CDATA[Recently we worked on a gradle project where we wanted to run integration tests for a soap client.]]></summary></entry><entry><title type="html">OSGi R7 Features</title><link href="https://rotty3000.doublebite.com/OSGi-R7-Features/" rel="alternate" type="text/html" title="OSGi R7 Features" /><published>2017-10-05T00:00:00+00:00</published><updated>2017-10-05T00:00:00+00:00</updated><id>https://rotty3000.doublebite.com/OSGi-R7-Features</id><content type="html" xml:base="https://rotty3000.doublebite.com/OSGi-R7-Features/"><![CDATA[<p>Here’s a brief rundown of new features added in OSGi R7 release (from core and compendium).</p>

<h3 id="new-developers-and-scm-metadata-headers">New <em>Developers</em> and <em>SCM</em> metadata headers:</h3>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">Bundle-Developers</span><span class="p">:</span> <span class="s">raymond.auge;email=raymond.auge@liferay.com;name="Raymond Augé"; organization="Liferay, Inc."</span>
<span class="py">Bundle-SCM</span><span class="p">:</span> <span class="s">url=https://github.com/liferay/foo,connection=scm:git:https://github.com/liferay/foo.git,developerConnection=scm:git:git@github.com:liferay/foo.git</span>
</code></pre></div></div>

<h3 id="manifest-annotations-build-time-annotations-designed-to-ease-metadata-maintenance">Manifest Annotations build-time annotations designed to ease metadata maintenance</h3>

<ul>
  <li>Manifest Annotations</li>
</ul>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">@Header</span> <span class="err">@Headers</span>
<span class="err">@Capability</span> <span class="err">@Capabilities</span>
<span class="err">@Requirement</span> <span class="err">@Requirements</span>
<span class="err">@Attribute</span> <span class="err">@Directive</span>
<span class="err">@Requirement.Resolution</span> <span class="err">@Requirement.Cardinality</span>
<span class="err">@Export</span> <span class="err">@Export.Substitution</span>
</code></pre></div></div>

<ul>
  <li>Arbitrary headers
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Headers</span><span class="o">({</span>
  <span class="nd">@Header</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"X-FooHeader"</span><span class="o">,</span> <span class="n">value</span> <span class="o">=</span> <span class="s">"foo"</span><span class="o">),</span>
  <span class="nd">@Header</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"X-FumHeader"</span><span class="o">,</span> <span class="n">value</span> <span class="o">=</span> <span class="s">"fum"</span><span class="o">)</span>
<span class="o">})</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">MyHeaders</span> <span class="o">{}</span>
</code></pre></div>    </div>
  </li>
  <li>User defined manifest header meta-annotations:
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Capability</span><span class="o">(</span><span class="n">namespace</span> <span class="o">=</span> <span class="s">"my.namespace"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">MyCapability</span> <span class="o">{</span>
  <span class="nd">@Attribute</span><span class="o">(</span><span class="s">"attr"</span><span class="o">)</span> <span class="nc">String</span> <span class="nf">value</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div>    </div>
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@MyCapability</span><span class="o">(</span><span class="s">"foo"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">MyClass</span> <span class="o">{}</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="101-log-service">101 Log Service</h3>

<ul>
  <li>Logging API: Logger, LoggerFactory, LogLevel
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Reference</span>
<span class="nc">LoggerFactory</span> <span class="n">loggerFactory</span><span class="o">;</span>
<span class="nc">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="n">loggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">Bar</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">logger</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Seems {} has erred"</span><span class="o">,</span> <span class="n">foo</span><span class="o">,</span> <span class="n">exception</span><span class="o">);</span>
</code></pre></div>    </div>
  </li>
  <li>Streaming log handler: LogStreamProvider
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Reference</span>
<span class="nc">LogStreamProvider</span> <span class="n">logStreamProvider</span><span class="o">;</span>
<span class="n">logStreamProvider</span><span class="o">.</span><span class="na">createStream</span><span class="o">()</span>
<span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">l</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">l</span><span class="o">))</span>
<span class="o">.</span><span class="na">onResolve</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"stream closed"</span><span class="o">));</span>
</code></pre></div>    </div>
  </li>
  <li>Logging configuration: LoggerAdmin, LoggerContext: declarative and programmatic
configuration
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Reference</span>
<span class="nc">LoggerAdmin</span> <span class="n">loggerAdmin</span><span class="o">;</span>
<span class="n">loggerAdmin</span><span class="o">.</span><span class="na">getLoggerContext</span><span class="o">(</span><span class="s">"com.foo.bar"</span><span class="o">).</span><span class="na">setLogLevels</span><span class="o">(...);</span>
</code></pre></div>    </div>
  </li>
  <li>example configuration for PID <code class="language-plaintext highlighter-rouge">org.osgi.service.log.admin|com.foo.bar</code>:
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>com.foo.bar.Impl=DEBUG
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="104-configuration-admin-service">104 Configuration Admin Service</h3>

<ul>
  <li>Named factory instances, read-only, passive update, pre-processed configurations
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Configuration</span> <span class="n">c</span> <span class="o">=</span> <span class="n">configurationAdmin</span><span class="o">.</span><span class="na">getFactoryConfiguration</span><span class="o">(</span>
  <span class="n">factoryPid</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="s">"?"</span><span class="o">);</span>
<span class="n">c</span><span class="o">.</span><span class="na">addAttributes</span><span class="o">(</span><span class="nc">ConfigurationAttribute</span><span class="o">.</span><span class="na">READ_ONLY</span><span class="o">);</span>
<span class="n">c</span><span class="o">.</span><span class="na">updateIfDifferent</span><span class="o">(</span><span class="n">properties</span><span class="o">);</span>
<span class="nc">Dictionary</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span><span class="nc">Object</span><span class="o">&gt;</span> <span class="n">d</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">getProcessedProperties</span><span class="o">(</span>
  <span class="nc">ServiceReference</span><span class="o">&lt;?&gt;</span> <span class="n">reference</span><span class="o">);</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="112-declarative-services">112 Declarative Services</h3>

<ul>
  <li>Activation/constructor injection
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Activate</span>
<span class="kd">public</span> <span class="nf">Foo</span><span class="o">(</span><span class="nd">@Reference</span> <span class="nc">Bar</span> <span class="n">bar</span><span class="o">)</span> <span class="o">{...}</span>
<span class="nd">@Activate</span>
<span class="kt">void</span> <span class="nf">activate</span><span class="o">(</span><span class="nc">Config</span> <span class="n">config</span><span class="o">,</span> <span class="nd">@Reference</span> <span class="nc">Bar</span> <span class="n">bar</span><span class="o">)</span> <span class="o">{...}</span>
</code></pre></div>    </div>
  </li>
  <li>Logger support
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Reference</span>
<span class="nc">Logger</span> <span class="n">logger</span><span class="o">;</span>
</code></pre></div>    </div>
  </li>
  <li>Factory properties
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span>
  <span class="n">factoryProperties</span><span class="o">=</span><span class="s">"OSGI-INF/factory.properties"</span><span class="o">,</span>
  <span class="n">factoryProperty</span><span class="o">=</span><span class="s">"hours:Integer=24"</span>
<span class="o">)</span>
</code></pre></div>    </div>
  </li>
  <li>Detect change to runtime via <code class="language-plaintext highlighter-rouge">service.changecount</code> runtime service property
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Reference</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"src"</span><span class="o">,</span> <span class="n">updated</span> <span class="o">=</span> <span class="s">"set"</span><span class="o">)</span>
<span class="kt">void</span> <span class="nf">set</span><span class="o">(</span><span class="nc">ServiceComponentRuntime</span> <span class="n">scr</span><span class="o">){...}</span>
</code></pre></div>    </div>
  </li>
  <li>Component property types
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ComponentPropertyType</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Config</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="nf">enabled</span><span class="o">()</span> <span class="k">default</span> <span class="kc">true</span><span class="o">;</span>
<span class="nc">String</span><span class="o">[]</span> <span class="nf">names</span><span class="o">()</span> <span class="k">default</span> <span class="o">{</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">};</span>
<span class="o">}</span>
<span class="nd">@Component</span>
<span class="nd">@Config</span><span class="o">(</span><span class="n">names</span><span class="o">=</span><span class="s">"myapp"</span><span class="o">)</span>
<span class="nd">@ServiceRanking</span><span class="o">(</span><span class="mi">100</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyComponent</span> <span class="o">{</span>
  <span class="nd">@Activate</span> <span class="kt">void</span> <span class="nf">activate</span><span class="o">(</span><span class="nc">Config</span> <span class="n">config</span><span class="o">)</span> <span class="o">{...}</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="added-support-for-jpa-21">Added support for JPA 2.1</h3>

<ul>
  <li>Standard configuration properties</li>
  <li>Updated configuration lifecycle rules</li>
  <li>New EntityManagerFactoryBuilder methods
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">n</span> <span class="o">=</span> <span class="n">entityManagerFactoryBuilder</span>
  <span class="o">.</span><span class="na">getPersistenceProviderName</span><span class="o">();</span>
<span class="nc">Bundle</span> <span class="n">b</span> <span class="o">=</span> <span class="n">entityManagerFactoryBuilder</span>
  <span class="o">.</span><span class="na">getPersistenceProviderBundle</span><span class="o">();</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="136-resolver-service">136 Resolver Service</h3>

<ul>
  <li>Define resolution of fragments
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Collection</span><span class="o">&lt;</span><span class="nc">Resource</span><span class="o">&gt;</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">resolveContext</span><span class="o">.</span><span class="na">findRelatedResources</span><span class="o">(</span><span class="n">resource</span><span class="o">);</span>
</code></pre></div>    </div>
  </li>
  <li>Define substitution wires
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Wire</span><span class="o">&gt;</span> <span class="n">ws</span> <span class="o">=</span> <span class="n">resolveContext</span><span class="o">.</span><span class="na">getSubstitutionWires</span><span class="o">(</span><span class="n">wiring</span><span class="o">);</span>
</code></pre></div>    </div>
  </li>
  <li>Cancelable resolution callback
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">resolveContext</span><span class="o">.</span><span class="na">onCancel</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span><span class="cm">/* do something */</span><span class="o">});</span>
</code></pre></div>    </div>
  </li>
  <li>Define dynamic resolving
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">resolver</span><span class="o">.</span><span class="na">resolveDynamic</span><span class="o">(</span><span class="n">resolveContext</span><span class="o">,</span> <span class="n">wiring</span><span class="o">,</span> <span class="n">requirement</span><span class="o">);</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="140-http-whiteboard">140 Http Whiteboard</h3>

<ul>
  <li>Standard Component property types
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span>
  <span class="n">scope</span> <span class="o">=</span> <span class="nc">ServiceScope</span><span class="o">.</span><span class="na">PROTOTYPE</span><span class="o">,</span> <span class="n">service</span> <span class="o">=</span> <span class="nc">Servlet</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@HttpWhiteboardServletPattern</span><span class="o">(</span><span class="s">"/as"</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">MyServlet</span> <span class="kd">extends</span> <span class="nc">HttpServlet</span> <span class="o">{}</span>
</code></pre></div>    </div>
  </li>
  <li>Multipart support
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span>
  <span class="n">scope</span> <span class="o">=</span> <span class="nc">ServiceScope</span><span class="o">.</span><span class="na">PROTOTYPE</span><span class="o">,</span> <span class="n">service</span> <span class="o">=</span> <span class="nc">Servlet</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@HttpWhiteboardServletPattern</span><span class="o">(</span><span class="s">"/as"</span><span class="o">)</span>
<span class="nd">@HttpWhiteboardServletMultipart</span><span class="o">(</span><span class="n">maxFileSize</span> <span class="o">=</span> <span class="mi">10000</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">MyServlet</span> <span class="kd">extends</span> <span class="nc">HttpServlet</span> <span class="o">{}</span>
</code></pre></div>    </div>
  </li>
  <li>Servlet pre-processors, execute before security, filter chain
finishSecurity method
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="nd">@HttpWhiteboardTarget</span><span class="o">(</span><span class="s">"(foo=bar)"</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">myPP</span> <span class="kd">implements</span> <span class="nc">Preprocessor</span> <span class="o">{...}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">handleSecurity</span><span class="o">(</span><span class="n">req</span><span class="o">,</span> <span class="n">res</span><span class="o">))</span> <span class="o">{</span>
  <span class="k">try</span> <span class="o">{</span><span class="cm">/* do filter chain/servlet */</span>
  <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
      <span class="n">finishSecurity</span><span class="o">(</span><span class="n">req</span><span class="o">,</span> <span class="n">res</span><span class="o">);</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>Detect changes via <code class="language-plaintext highlighter-rouge">service.changecount</code> runtime service property
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Reference</span><span class="o">(</span><span class="n">updated</span> <span class="o">=</span> <span class="s">"setHttpServiceRuntime"</span><span class="o">,</span> <span class="n">policy</span> <span class="o">=</span> <span class="no">DYNAMIC</span><span class="o">,</span> <span class="n">policyOption</span> <span class="o">=</span> <span class="no">GREEDY</span><span class="o">)</span>
<span class="kt">void</span> <span class="nf">setHttpServiceRuntime</span><span class="o">(</span><span class="nc">HttpServiceRuntime</span> <span class="n">hsr</span><span class="o">){</span>
  <span class="c1">// has changed</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>HttpService integration
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span>
  <span class="n">scope</span><span class="o">=</span><span class="nc">ServiceScope</span><span class="o">.</span><span class="na">PROTOTYPE</span><span class="o">,</span> <span class="n">service</span> <span class="o">=</span> <span class="nc">Filter</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@HttpWhiteboardFilterPattern</span><span class="o">(</span><span class="s">"/*"</span><span class="o">)</span>
<span class="nd">@HttpWhiteboardContextSelect</span><span class="o">(</span>
  <span class="s">"(osgi.http.whiteboard.context.httpservice=*)"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyFilter</span> <span class="kd">implements</span> <span class="nc">Filter</span> <span class="o">{...}</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="147-transaction-control-service-new">147 Transaction Control Service <em>NEW</em></h3>

<ul>
  <li>Portable, modular abstraction for Transaction lifecycle management
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="nc">TransactionControl</span> <span class="n">tc</span><span class="o">;</span>
<span class="kd">final</span> <span class="nc">Connection</span> <span class="n">connection</span><span class="o">;</span>
<span class="nd">@Activate</span> <span class="kd">public</span> <span class="nf">Messenger</span><span class="o">(</span>
  <span class="nd">@Reference</span> <span class="nc">TransactionControl</span> <span class="n">tc</span><span class="o">,</span>
  <span class="nd">@Reference</span> <span class="nc">JDBCConnectionProvider</span> <span class="n">provider</span><span class="o">)</span> <span class="o">{</span>
  <span class="k">this</span><span class="o">.</span><span class="na">tc</span> <span class="o">=</span> <span class="n">tc</span><span class="o">;</span>
  <span class="k">this</span><span class="o">.</span><span class="na">connection</span> <span class="o">=</span> <span class="n">provider</span><span class="o">.</span><span class="na">getResrouce</span><span class="o">(</span><span class="n">control</span><span class="o">)</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addMessage</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
  <span class="n">tc</span><span class="o">.</span><span class="na">required</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="c1">//start scope</span>
      <span class="nc">PreparedStatement</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">prepareStatement</span><span class="o">(</span>
          <span class="s">"Insert into MESSAGE values ( ? )"</span><span class="o">);</span>
      <span class="n">ps</span><span class="o">.</span><span class="na">setString</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">message</span><span class="o">);</span>
      <span class="k">return</span> <span class="n">ps</span><span class="o">.</span><span class="na">executeUpdate</span><span class="o">();</span>
  <span class="o">});</span> <span class="c1">// end scope</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>Allow different resource types to be easily used within a Transaction</li>
  <li>Exception handling</li>
  <li>Rollback avoidance</li>
  <li>Multithreading</li>
  <li>Associate resources with scope</li>
  <li>TX lifecycle callbacks</li>
  <li>Enlist resources with TX</li>
  <li>Mark TX for rollback</li>
  <li>Local &amp; XA</li>
  <li>Builder pattern</li>
  <li>Resource providers</li>
  <li>etc.</li>
</ul>

<h3 id="148-cluster-information-new">148 Cluster Information <em>NEW</em></h3>

<p>API for a management agent to discover, list and inspect
available nodes in the cluster.</p>

<ul>
  <li>Property Prefix: osgi.clusterinfo.</li>
  <li>NodeStatus service properties:
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">m</span> <span class="o">=</span> <span class="n">nodeStatus</span><span class="o">.</span><span class="na">getMetrics</span><span class="o">(</span><span class="n">names</span><span class="o">);</span>
</code></pre></div>    </div>
    <ul>
      <li>id</li>
      <li>cluster</li>
      <li>parent</li>
      <li>endpoint</li>
      <li>endpoint.private</li>
      <li>vendor</li>
      <li>version</li>
      <li>country</li>
      <li>location</li>
      <li>region</li>
      <li>zone</li>
      <li>tags</li>
    </ul>
  </li>
  <li>FrameworkNodeStatus properties:
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">FrameworkNodeStatus</span>
  <span class="kd">extends</span> <span class="nc">FrameworkManager</span><span class="o">,</span> <span class="nc">NodeStatus</span> <span class="o">{}</span>

<span class="nc">BundleDTO</span> <span class="n">bdto</span> <span class="o">=</span> <span class="n">frameworkNodeStatus</span><span class="o">.</span><span class="na">installBundle</span><span class="o">(</span><span class="n">location</span><span class="o">);</span>
</code></pre></div>    </div>
    <ul>
      <li>org.osgi.framework.version</li>
      <li>org.osgi.framework.processor</li>
      <li>org.osgi.framework.os_name</li>
      <li>java.version</li>
      <li>java.runtime.version</li>
      <li>java.specification.version</li>
      <li>java.vm.version</li>
    </ul>
  </li>
</ul>

<h3 id="150-configurator-new">150 Configurator <em>NEW</em></h3>

<p>Feed configurations into Configuration Admin through</p>

<ul>
  <li>configuration resources, multiple PIDs, multiple</li>
  <li>configuration resources, extender pattern, UTF-8 encoded</li>
  <li>JSON resources, installed via bundles e.g. <code class="language-plaintext highlighter-rouge">OSGI-INF/configurator/foo.json</code>:
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="err">//</span><span class="w"> </span><span class="err">Global</span><span class="w"> </span><span class="err">Settings</span><span class="w">
  </span><span class="nl">":configurator:resource-version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"pid.a"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"val"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"some_number"</span><span class="p">:</span><span class="w"> </span><span class="mi">123</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"factory.pid.b~name"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"a_boolean"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>Ranking,  factory configs, typed, binary data, overwrite
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"my.config"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"security.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"public.key:binary"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"/OSGI-INF/files/mykey.pub"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>policy, well defined lifecycle, via system property, coordinator
<code class="language-plaintext highlighter-rouge">-Dconfigurator.initial={"pid.a": {"some_number": 123}}</code>
<code class="language-plaintext highlighter-rouge">-Dconfigurator.initial=file:/some.json,file:/other.json</code></li>
  <li>integration, standalone configurations</li>
</ul>

<h3 id="151-jax-rs-whiteboard-new">151 JAX-RS Whiteboard <em>NEW</em></h3>

<ul>
  <li>Register JAX-RS annotated POJO (resources), Applications,</li>
  <li>Extensions as services, simple collaboration model
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span><span class="o">(</span><span class="n">service</span> <span class="o">=</span> <span class="nc">Foo</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@JaxrsName</span><span class="o">(</span><span class="s">"foo"</span><span class="o">)</span> <span class="nd">@JaxrsResource</span> <span class="nd">@Path</span><span class="o">(</span><span class="s">"foo"</span><span class="o">)</span>
<span class="kd">class</span> <span class="nc">Foo</span> <span class="o">{</span>
  <span class="nd">@GET</span> <span class="nd">@Path</span><span class="o">(</span><span class="s">"{name}"</span><span class="o">)</span>
  <span class="kd">public</span> <span class="nc">String</span> <span class="nf">interrogateSession</span><span class="o">(</span>
      <span class="nd">@PathParam</span><span class="o">(</span><span class="s">"name"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">name</span><span class="o">,</span>
      <span class="nd">@Context</span> <span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">)</span> <span class="o">{</span>

     <span class="nc">HttpSession</span> <span class="n">s</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="na">getSession</span><span class="o">();</span>
     <span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">getAttribute</span><span class="o">(</span><span class="n">name</span><span class="o">));</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>Require extensions, static resources, multiple whiteboards with own endpoints, simplified default applications,
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="nd">@JaxrsExtensionSelect</span><span class="o">(</span><span class="s">"(osgi.jaxrs.name=configProvider)"</span><span class="o">)</span>
</code></pre></div>    </div>
  </li>
  <li>Detect runtime changes via <code class="language-plaintext highlighter-rouge">service.changecount</code> runtime service property:
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Reference</span><span class="o">(</span><span class="n">updated</span> <span class="o">=</span> <span class="s">"setJaxRSServiceRuntime"</span><span class="o">,</span> <span class="n">policy</span> <span class="o">=</span> <span class="no">DYNAMIC</span><span class="o">,</span> <span class="n">policyOption</span> <span class="o">=</span> <span class="no">GREEDY</span><span class="o">)</span>
<span class="kt">void</span> <span class="nf">setJaxRSServiceRuntime</span><span class="o">(</span><span class="nc">JaxRSServiceRuntime</span> <span class="n">runtime</span><span class="o">){</span>
  <span class="c1">// has changed</span>
<span class="o">}</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="705-promises">705 Promises</h3>

<ul>
  <li>Thrown exceptions in Function and Predicate, timeout/delay, specify executor/scheduledExecutor, support Callback from org.osgi.util.function
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="nc">Promise</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">answer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Deferred</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;(</span>
  <span class="n">executor</span><span class="o">,</span> <span class="n">scheduledExecutor</span>
<span class="o">).</span><span class="na">getPromise</span><span class="o">().</span><span class="na">delay</span><span class="o">(</span><span class="mi">1000</span><span class="o">).</span><span class="na">timeout</span><span class="o">(</span><span class="mi">5000</span><span class="o">).</span><span class="na">then</span><span class="o">(</span>
  <span class="k">new</span> <span class="nf">Callback</span><span class="o">()</span> <span class="o">{</span>
      <span class="nd">@Override</span>
      <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
          <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">answer</span><span class="o">);</span>
      <span class="o">}</span>
  <span class="o">}</span>
<span class="o">);</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="706-push-stream-new">706 Push Stream <em>NEW</em></h3>

<ul>
  <li>Compact pipeline asynchronous tasks on event arrival, circuit breaker, finite streams &amp; back pressure explicit in API signatures, inspired by Java 8 Streams API, independent, generics, lazy,  composed by functional steps, mapping, flat mapping, filtering, stateless &amp; stateful intermediate operations
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">PushEventSource</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">source</span> <span class="o">=</span> <span class="o">...</span>
<span class="nc">Integer</span> <span class="n">i</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PushStreamProvider</span><span class="o">().</span><span class="na">buildStream</span><span class="o">(</span><span class="n">source</span><span class="o">)</span>
  <span class="o">.</span><span class="na">withPushbackPolicy</span><span class="o">(</span><span class="no">LINEAR</span><span class="o">,</span> <span class="mi">20</span><span class="o">)</span>
  <span class="o">.</span><span class="na">withQueuePolicy</span><span class="o">(</span><span class="no">FAIL</span><span class="o">)</span>
  <span class="o">.</span><span class="na">build</span><span class="o">()</span>
  <span class="o">.</span><span class="na">max</span><span class="o">(</span><span class="nl">Integer:</span><span class="o">:</span><span class="n">compare</span><span class="o">)</span>
  <span class="o">.</span><span class="na">getValue</span><span class="o">().</span><span class="na">get</span><span class="o">();</span>
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="707-converter-new">707 Converter <em>NEW</em></h3>

<ul>
  <li>Make it easy to convert many types to other types, scalars, collections, maps, beans, interfaces and DTOs, no boilerplate conversion code, customized using converter builders, generics, rules
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Converter</span> <span class="n">c</span> <span class="o">=</span> <span class="nc">Converters</span><span class="o">.</span><span class="na">standardConverter</span><span class="o">();</span>
<span class="nc">MyEnum</span> <span class="n">e</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">convert</span><span class="o">(</span><span class="nc">MyOtherEnum</span><span class="o">.</span><span class="na">BLUE</span><span class="o">).</span><span class="na">to</span><span class="o">(</span><span class="nc">MyEnum</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">BigDecimal</span> <span class="n">bd</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">convert</span><span class="o">(</span><span class="mi">12345</span><span class="o">).</span><span class="na">to</span><span class="o">(</span><span class="nc">BigDecimal</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="kt">long</span><span class="o">[]</span> <span class="n">la</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">convert</span><span class="o">(</span>
  <span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="s">"978"</span><span class="o">,</span> <span class="s">"142"</span><span class="o">)).</span><span class="na">to</span><span class="o">(</span><span class="kt">long</span><span class="o">[].</span><span class="na">class</span><span class="o">);</span>
<span class="nc">Map</span> <span class="n">m</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">singletonMap</span><span class="o">(</span><span class="s">"timeout"</span><span class="o">,</span> <span class="s">"700"</span><span class="o">);</span>
<span class="kt">int</span> <span class="n">t</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">convert</span><span class="o">(</span><span class="n">m</span><span class="o">).</span><span class="na">to</span><span class="o">(</span><span class="nc">MyInterface</span><span class="o">.</span><span class="na">class</span><span class="o">).</span><span class="na">timeout</span><span class="o">();</span>
<span class="nc">Map</span> <span class="n">m</span> <span class="o">=</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">singletonMap</span><span class="o">(</span><span class="s">"args"</span><span class="o">,</span> <span class="kc">null</span><span class="o">)</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">a1</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">convert</span><span class="o">(</span><span class="n">m</span><span class="o">).</span><span class="na">to</span><span class="o">(</span><span class="nc">MyAnnotation</span><span class="o">.</span><span class="na">class</span><span class="o">).</span><span class="na">args</span><span class="o">();</span>
</code></pre></div>    </div>
  </li>
</ul>]]></content><author><name>Raymond Augé</name></author><summary type="html"><![CDATA[Here's a brief rundown of new features added in OSGi R7 release (from core and compendium).]]></summary></entry></feed>