<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[atchison.dev]]></title><description><![CDATA[atchison.dev]]></description><link>https://atchison.dev/</link><image><url>https://atchison.dev/favicon.png</url><title>atchison.dev</title><link>https://atchison.dev/</link></image><generator>Ghost 3.2</generator><lastBuildDate>Sat, 18 Apr 2026 06:54:44 GMT</lastBuildDate><atom:link href="https://atchison.dev/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Using Docker Auth in Containerd]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>File this under things that should not be this complicated.</p>
<p>I am being rate limited by Docker Hub due to unauthenticated requests. I want to use my docker login / authentication to get a higher limit.</p>
<p>Containerd cannot use the plaintext credentials directly from <code>$HOME/.docker/config.json.</code></p>
<p>There is conflicting</p>]]></description><link>https://atchison.dev/using-docker-auth-in-containerd/</link><guid isPermaLink="false">69a708a67e64056f8f8c63e3</guid><category><![CDATA[docker]]></category><category><![CDATA[containerd]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Tue, 03 Mar 2026 17:32:06 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>File this under things that should not be this complicated.</p>
<p>I am being rate limited by Docker Hub due to unauthenticated requests. I want to use my docker login / authentication to get a higher limit.</p>
<p>Containerd cannot use the plaintext credentials directly from <code>$HOME/.docker/config.json.</code></p>
<p>There is conflicting guidance online and how this works across versions 1 and 2 of containerd itself as well as versions 2 and 3 of the toml configuration.</p>
<p>Executive summary:</p>
<p>If you are interacting with a registry that does bearer auth flow (like Docker Hub), you cannot use the 'new' <code>/etc/containerd/certs.d/docker.io/hosts.toml</code>.</p>
<p>This will not work for docker hub:</p>
<pre><code class="language-toml">server = &quot;https://docker.io&quot;

[host.&quot;https://registry-1.docker.io&quot;]
  capabilities = [&quot;pull&quot;, &quot;resolve&quot;]

  [host.&quot;https://registry-1.docker.io&quot;.header]
    Authorization = &quot;Basic BASE64_OF_USERNAME_COLON_TOKEN&quot;
</code></pre>
<p>Even though there are <a href="https://github.com/containerd/containerd/issues/8186">multiple</a> <a href="https://github.com/containerd/containerd/blob/main/docs/hosts.md">places</a> that say this is the <a href="https://github.com/containerd/containerd/blob/main/docs/cri/registry.md">new way forward</a>. For custom registries that would accept a static header, this probably does work for them.</p>
<p>This warning is listed prominently in the documentation:</p>
<pre><code>NOTE: registry.configs.*.auth is DEPRECATED and will NOT have an equivalent way to store unencrypted secrets in the host configuration files. However, it will not be removed until a suitable secret management alternative is available as a plugin. It remains supported in 1.x releases, including the 1.6 LTS release.
</code></pre>
<p>Which is not true when read literally. The new hosts files do have mechanisms to store plaintext credentials and pass them along, just not in the same way that <code>registry.configs</code> did, which is a massive distinction that is not written down anywhere.</p>
<p>The correct way to use existing docker authentication for containerd version 2 / toml version 3 is:</p>
<pre><code class="language-toml">[plugins.&quot;io.containerd.cri.v1.images&quot;.registry.configs.&quot;registry-1.docker.io&quot;.auth]
  auth = &quot;BASE64_OF_USERNAME_COLON_TOKEN&quot;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Overriding a Bitnami Subchart]]></title><description><![CDATA[<p>With <a href="https://github.com/bitnami/charts/issues/35164">Bitnami moving to a paid model</a>, we migrated all of our existing Bitnami OSS items to the <a href="https://hub.docker.com/u/bitnamilegacy">bitnamilegacy repository</a>. One issue we ran into though, even though Elasticsearch itself migrated to bitnamilegacy, any underlying images it was using are still from the regular Bitnami repository since there has been</p>]]></description><link>https://atchison.dev/overriding-a-bitnami-subchart/</link><guid isPermaLink="false">697b90b97e64056f8f8c6394</guid><category><![CDATA[Bitnami]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Helm]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Thu, 29 Jan 2026 20:09:16 GMT</pubDate><content:encoded><![CDATA[<p>With <a href="https://github.com/bitnami/charts/issues/35164">Bitnami moving to a paid model</a>, we migrated all of our existing Bitnami OSS items to the <a href="https://hub.docker.com/u/bitnamilegacy">bitnamilegacy repository</a>. One issue we ran into though, even though Elasticsearch itself migrated to bitnamilegacy, any underlying images it was using are still from the regular Bitnami repository since there has been no new release to perform migration under the hood.</p><p>As part of its initContainers, Elastic pulls in os-shell. The question became, how do we specify a different repository for os-shell when we are pulling in the chart externally (and not pulling from disk ourselves). The solution was deceptively simple:</p><!--kg-card-begin: markdown--><pre><code class="language-yaml">elasticsearch:
  sysctlImage:
    repository: bitnamilegacy/os-shell
</code></pre>
<!--kg-card-end: markdown--><p>What this hides is the rube goldberg machine Bitnami setup to process the above into an image/repository/tag setting you would normally see:</p><!--kg-card-begin: markdown--><p>Unpacking the elasticsearch helm chart, we find in<br>
<code>templates/ingest/statefulset.yaml</code>:</p>
<pre><code class="language-yaml">      initContainers:
        {{- if and .Values.sysctlImage.enabled .Values.enableDefaultInitContainers }}
        ## Image that performs the sysctl operation to modify Kernel settings (needed sometimes to avoid boot errors)
        - name: sysctl
          image: {{ include &quot;elasticsearch.sysctl.image&quot; . }}
          imagePullPolicy: {{ .Values.image.pullPolicy | quote }}
          command:
            - /bin/bash
            - -ec
            - |
              {{- include &quot;elasticsearch.sysctlIfLess&quot; (dict &quot;key&quot; &quot;vm.max_map_count&quot; &quot;value&quot; &quot;262144&quot;) | nindent 14 }}
              {{- include &quot;elasticsearch.sysctlIfLess&quot; (dict &quot;key&quot; &quot;fs.file-max&quot; &quot;value&quot; &quot;65536&quot;) | nindent 14 }}
          securityContext:
            privileged: true
            runAsUser: 0
          {{- if .Values.sysctlImage.resources }}
          resources: {{- toYaml .Values.sysctlImage.resources | nindent 12 }}
          {{- else if ne .Values.sysctlImage.resourcesPreset &quot;none&quot; }}
          resources: {{- include &quot;common.resources.preset&quot; (dict &quot;type&quot; .Values.sysctlImage.resourcesPreset) | nindent 12 }}
          {{- end }}
</code></pre>
<p>You might think that we then need:</p>
<pre><code class="language-yaml">elasticsearch:
  sysctlImage:
    enabled: true
  sysctl:
    image: ...
</code></pre>
<p>But no! That is a helper function!</p>
<p>In <code>templates/_helpers.tpl</code>:</p>
<pre><code class="language-yaml">{{/*
Return the proper sysctl image name
*/}}
{{- define &quot;elasticsearch.sysctl.image&quot; -}}
{{ include &quot;common.images.image&quot; (dict &quot;imageRoot&quot; .Values.sysctlImage &quot;global&quot; .Values.global) }}
{{- end -}} 
</code></pre>
<p>But wait, what is &quot;common.images.image&quot;!?</p>
<p>Another template that comes from the <a href="https://github.com/bitnami/charts/blob/main/bitnami/common/templates/_images.tpl">bitnami-common chart</a>:</p>
<pre><code class="language-yaml">{{- define &quot;common.images.image&quot; -}}
{{- $registryName := default .imageRoot.registry ((.global).imageRegistry) -}}
{{- $repositoryName := .imageRoot.repository -}}
{{- $separator := &quot;:&quot; -}}
{{- $termination := .imageRoot.tag | toString -}}

{{- if not .imageRoot.tag }}
  {{- if .chart }}
    {{- $termination = .chart.AppVersion | toString -}}
  {{- end -}}
{{- end -}}
{{- if .imageRoot.digest }}
    {{- $separator = &quot;@&quot; -}}
    {{- $termination = .imageRoot.digest | toString -}}
{{- end -}}
{{- if $registryName }}
    {{- printf &quot;%s/%s%s%s&quot; $registryName $repositoryName $separator $termination -}}
{{- else -}}
    {{- printf &quot;%s%s%s&quot;  $repositoryName $separator $termination -}}
{{- end -}}
{{- end -}}
</code></pre>
<p>Yaml is the worst.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ubuntu 24.04 NVIDIA Driver Install]]></title><description><![CDATA[<p>What a nightmare!</p><p>I solved this once when several months ago when I got new hardware and did not write down the instructions. Kernel did an update, now my drivers do not work. After some head wall bashing for a day, I have solved the problem again. For reference, I</p>]]></description><link>https://atchison.dev/ubuntu-24-04-nvidia-driver-install/</link><guid isPermaLink="false">6821f78d96d7d13790b9de89</guid><category><![CDATA[NVIDIA]]></category><category><![CDATA[Ubuntu]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Mon, 12 May 2025 13:43:00 GMT</pubDate><content:encoded><![CDATA[<p>What a nightmare!</p><p>I solved this once when several months ago when I got new hardware and did not write down the instructions. Kernel did an update, now my drivers do not work. After some head wall bashing for a day, I have solved the problem again. For reference, I have a 5080 RTX.</p><p>Here is the most straightforward way to have Nvidia drivers work for Ubuntu 24.04 with secure boot enabled:</p><!--kg-card-begin: markdown--><pre><code class="language-bash"># purge any and all existing nvidia installations
sudo apt purge --purge nvidia-*
sudo apt purge --purge libnvidia-*
# Download NVIDIA-Linux-x86_64-570.86.16.run from https://www.nvidia.com/en-us/drivers/details/240524/
# Run this installer
sudo ./NVIDIA-Linux-x86_64-570.86.16.run
</code></pre>
<ul>
<li>Choose MIT/GPL (non proprietary)</li>
<li>Yes to continuing the installation on any prompt that requests it.</li>
<li>Yes sign the module. Use a new key. Write down the location of the .der file it tells you. The path will look like <code>/usr/share/nvidia/nvidia-modsign-crt-HEX.der</code></li>
<li>Yes to update/overwrite X server settings.</li>
</ul>
<p>Before you restart your computer, you will need to load the public key (der file above) into the Machine Owner Key list.</p>
<pre><code class="language-bash">sudo mokutil --import /usr/share/nvidia/nvidia-modsign-crt-HEX.der
</code></pre>
<ul>
<li>
<p>It will then prompt you for a password. Type it in twice.</p>
</li>
<li>
<p>Reboot.</p>
</li>
<li>
<p>On reboot, the system will ask you if you want to examine new keys, do so, the signature should match what you were told in the installation (fingerprint, etc...).</p>
</li>
<li>
<p>You will be prompted for a password to load the module, use the password you made earlier.</p>
</li>
</ul>
<p>Your nvidia linux drivers should now be loaded and working.</p>
<p>Confirm with <code>nvidia-smi</code>.</p>
<pre><code>+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.86.16              Driver Version: 570.86.16      CUDA Version: 12.8     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 5080        Off |   00000000:01:00.0  On |                  N/A |
|  0%   29C    P8             26W /  360W |     507MiB /  16303MiB |      1%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|    0   N/A  N/A            3046      G   /usr/lib/xorg/Xorg                      280MiB |
|    0   N/A  N/A            3428      G   /usr/bin/gnome-shell                     47MiB |
|    0   N/A  N/A            4670      G   /opt/google/chrome/chrome                 4MiB |
|    0   N/A  N/A            4738      G   ...ersion=20250511-180108.192000        120MiB |
+-----------------------------------------------------------------------------------------+
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ubuntu 24 Terminal Not Starting Immediately]]></title><description><![CDATA[<p>New OS, who dis?</p><p>After a hardware upgrade, I installed Ubuntu 24 to dual boot with Windows. On startup, I noticed that the terminal would not imediately start. Chrome would immediately start, but network connections would hang. This eventually resolves within a minute or so, but this is completely unacceptable.</p>]]></description><link>https://atchison.dev/ubuntu-24-terminal-not-starting-immediately/</link><guid isPermaLink="false">67e03de096d7d13790b9de4b</guid><category><![CDATA[Ubuntu]]></category><category><![CDATA[Operating Systems]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Sun, 23 Mar 2025 17:45:14 GMT</pubDate><content:encoded><![CDATA[<p>New OS, who dis?</p><p>After a hardware upgrade, I installed Ubuntu 24 to dual boot with Windows. On startup, I noticed that the terminal would not imediately start. Chrome would immediately start, but network connections would hang. This eventually resolves within a minute or so, but this is completely unacceptable.</p><p>Courtesy of this <a href="https://www.reddit.com/r/Ubuntu/comments/1cdondr/comment/lqjve1p/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button">Reddit comment</a> / <a href="https://askubuntu.com/questions/1529418/some-applications-start-very-slow-after-boot-on-ubuntu-24-04">stackoverflow post</a>: </p><pre><code class="language-bash">sudo apt remove xdg-desktop-portal-gnome</code></pre><p>was the solution.</p><p>Looking at journalctl prior to running the above remove command:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">Mar 23 10:12:23 knowles-AMD systemd[2981]: Started xdg-desktop-portal-gtk.service - Portal service (GTK/GNOME implementation).
...
Mar 23 10:13:03 knowles-AMD systemd[2981]: xdg-desktop-portal.service: start operation timed out. Terminating.
Mar 23 10:13:03 knowles-AMD systemd[2981]: xdg-desktop-portal.service: Failed with result 'timeout'.
Mar 23 10:13:03 knowles-AMD systemd[2981]: Failed to start xdg-desktop-portal.service - Portal service.
</code></pre>
<!--kg-card-end: markdown--><p>No side effects or issues thus far. Only remove the one package, do not remove anything that stems from it dependency wise.</p>]]></content:encoded></item><item><title><![CDATA[Mounting Your Google Drive in Google Colab]]></title><description><![CDATA[<ol><li>Go to <a href="https://colab.research.google.com/">https://colab.research.google.com/</a> <br>Create a new notebook (I have mine saved in Drive)</li><li>Create a code cell and run the following code:</li></ol><!--kg-card-begin: markdown--><pre><code class="language-python">from google.colab import drive
drive.mount('/content/drive')
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeBvYHI6lNooslRGP_V4Q-665yxO5SkM3CHlGuFZn0yFH7rnfD0yIVxDHMDixoVtjOAESijOcjvjmhNU5zyd0KIGstiYUxr6SMqXgJhw538c1uP8AHUqeyn1Y0WjS0UQt-9O82-uw?key=64DJYPknzY5CCW9fXVNeE6MY" class="kg-image"></figure><p>You will be asked to give Colab permission to access your Drive. If you</p>]]></description><link>https://atchison.dev/mounting-your-google-drive-in-google-colab/</link><guid isPermaLink="false">67a1f5126258153a1fdf7970</guid><category><![CDATA[Python]]></category><category><![CDATA[jupyter]]></category><category><![CDATA[Education]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Tue, 04 Feb 2025 11:22:37 GMT</pubDate><content:encoded><![CDATA[<ol><li>Go to <a href="https://colab.research.google.com/">https://colab.research.google.com/</a> <br>Create a new notebook (I have mine saved in Drive)</li><li>Create a code cell and run the following code:</li></ol><!--kg-card-begin: markdown--><pre><code class="language-python">from google.colab import drive
drive.mount('/content/drive')
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeBvYHI6lNooslRGP_V4Q-665yxO5SkM3CHlGuFZn0yFH7rnfD0yIVxDHMDixoVtjOAESijOcjvjmhNU5zyd0KIGstiYUxr6SMqXgJhw538c1uP8AHUqeyn1Y0WjS0UQt-9O82-uw?key=64DJYPknzY5CCW9fXVNeE6MY" class="kg-image"></figure><p>You will be asked to give Colab permission to access your Drive. If you have previously given permission, you still need to click through the entire prompt.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXclupPMQAYPuDNa8J8S5i2bvQfH-D1NxhmcEyVBDv9s2xwHnXYic3hQtplv9Dn26VnebPjfk8S0Jx6V75zyt13B1tLrlUcRsyYbXI5hELWqum3ZzySr5nieF6j44k_sNF8QP8uy_A?key=64DJYPknzY5CCW9fXVNeE6MY" class="kg-image"></figure><p>If this is successful, you will see your drive mounted in /content/drive in the file explorer on the left side of the notebook:</p><figure class="kg-card kg-image-card"><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfHfVfWUjVwkCyHLi3dlFH0L8AOyxyUxle32galHDkABa1TBcCRAG1RRNTVtDVLCGP6fzPN-vknF0Bp-udN-BAbWmGELA0YQBVcCOkBLJuFz9vyMDeC5gRqilpFRlib3v75T0TdeQ?key=64DJYPknzY5CCW9fXVNeE6MY" class="kg-image"></figure><p>You should now be able to browse your files.</p><p><strong>NOTE</strong>: If you want to share something from ‘Shared with Me’ or something that is not in the root level of your drive, the easiest way to access this is to make a shortcut and put it in your root level drive directory:</p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2025/02/ghost-blog-mount-shortcut.png" class="kg-image"></figure><p>It will then show up in the file explorer to the left of the notebook:</p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2025/02/ghost-mount-colab-list.png" class="kg-image"></figure><p>It looks like this in your actual Drive UI:</p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2025/02/ghost-mount-drive-list.png" class="kg-image"></figure><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2025/02/ghost-mount-drive-list2.png" class="kg-image"></figure><!--kg-card-begin: markdown--><p>You can then interact with the files with normal OS python code, such as:</p>
<pre><code class="language-python">import os

directory_path = '/content/drive/MyDrive/some/folder'

def list_files_recursively(f, path):
   for root, dirs, files in os.walk(path):
       for file in files:
           f.write(os.path.join(root, file))

with open('/content/drive/MyDrive/file-listing.txt', 'w') as f:
 list_files_recursively(f, directory_path)

</code></pre>
<!--kg-card-end: markdown--><p></p>]]></content:encoded></item><item><title><![CDATA[OpenAPI Post Generation Certificate Authority Trust]]></title><description><![CDATA[<p>I truly despise certificate eco systems. Every language does it a little bit differently, libraries use different environment variables, and operating systems are ever so slightly different (looking at you Ubuntu and RHEL).</p><p>Thankfully, libraries that have become defacto standard lib have well known configuration. </p><p>i.e. The requests python</p>]]></description><link>https://atchison.dev/openapi-post-generation-certificate-authority-trust/</link><guid isPermaLink="false">67604fa16258153a1fdf7929</guid><category><![CDATA[Python]]></category><category><![CDATA[OpenAPI]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Wed, 18 Dec 2024 02:16:49 GMT</pubDate><content:encoded><![CDATA[<p>I truly despise certificate eco systems. Every language does it a little bit differently, libraries use different environment variables, and operating systems are ever so slightly different (looking at you Ubuntu and RHEL).</p><p>Thankfully, libraries that have become defacto standard lib have well known configuration. </p><p>i.e. The requests python library uses </p><pre><code class="language-py">export REQUESTS_CA_BUNDLE=/path/to/bundle</code></pre><p>I was running into an issue where an application was using an OpenAPI generated API in python. When attempting to connect to our service using an internally signed certificate, it failed validation. </p><p>No worries, I thought, this happens all the time, I just need to find what environment variable OpenAPI needs to add this post code generation / at runtime.</p><p><strong>This does not exist.</strong></p><p>We are admittedly using an older version (4.3.1) of the <code>openapi-generator-cli</code>. But this configuration option does not exist in the latest version (7.10.0).</p><p>For version 5, <a href="https://github.com/OpenAPITools/openapi-generator/pull/8108">they did make it better</a> where the <code>ssl_ca_cert</code> will be None and this will force <code>urllib3</code> to load the system certificates (this would solve my problem as we already update the OS CA bundles in the Dockerfile). </p><p>I would prefer to see it default to an environment variable check, falling back to None if it doesn't exist, to then fall back on the OS CA bundle. This would avoid code updates when trust needs to be changed.</p>]]></content:encoded></item><item><title><![CDATA[Returning Keycloak Group Info]]></title><description><![CDATA[<p>It is mind boggling that group membership information is not returned by default by <a href="https://www.keycloak.org/">Keycloak</a> for a user claim/token. You have to manually go into Client scopes and add it to the profile section.</p><p>Client scope -&gt; click on profile </p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2024/12/keycloak1.png" class="kg-image"></figure><p>Mappers tab -&gt; Add Mapper (By configuration)</p>]]></description><link>https://atchison.dev/returning-keycloak-group-info/</link><guid isPermaLink="false">67604d3e6258153a1fdf7903</guid><category><![CDATA[Keycloak]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Mon, 16 Dec 2024 16:02:28 GMT</pubDate><content:encoded><![CDATA[<p>It is mind boggling that group membership information is not returned by default by <a href="https://www.keycloak.org/">Keycloak</a> for a user claim/token. You have to manually go into Client scopes and add it to the profile section.</p><p>Client scope -&gt; click on profile </p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2024/12/keycloak1.png" class="kg-image"></figure><p>Mappers tab -&gt; Add Mapper (By configuration):</p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2024/12/keycloak2.png" class="kg-image"></figure><p>Click on Group Membership</p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2024/12/keycloak3.png" class="kg-image"></figure><p>Fill out the mapper details. The Token Claim Name is what your backend code is looking for in the json (if you do not control this, it needs to match).</p><figure class="kg-card kg-image-card"><img src="https://atchison.dev/content/images/2024/12/keycloak4.png" class="kg-image"></figure><p>Happy integrating!</p>]]></content:encoded></item><item><title><![CDATA[Upgrading Spring Security from 5 to 6]]></title><description><![CDATA[<p></p><p>Upgrading a "legacy" Spring 5 / Java 11 SPA to Spring 6 / Java 17 was a bit of a to do.</p><p>Things you may run into with Spring Security:</p><!--kg-card-begin: markdown--><ol>
<li><a href="https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#_stop_using_websecurityconfigureradapter">WebSecurityConfigurerAdapter</a> is gone.</li>
<li><a href="https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#use-new-requestmatchers">Ant matchers no longer exist</a>. You have to set a config item to use the previous ant path matching</li></ol>]]></description><link>https://atchison.dev/upgrading-spring-security-from-5-to-6/</link><guid isPermaLink="false">66d1f1c0b3f1a4704e10ee5f</guid><category><![CDATA[Java]]></category><category><![CDATA[Spring]]></category><category><![CDATA[Spring Boot]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Fri, 30 Aug 2024 17:24:19 GMT</pubDate><content:encoded><![CDATA[<p></p><p>Upgrading a "legacy" Spring 5 / Java 11 SPA to Spring 6 / Java 17 was a bit of a to do.</p><p>Things you may run into with Spring Security:</p><!--kg-card-begin: markdown--><ol>
<li><a href="https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#_stop_using_websecurityconfigureradapter">WebSecurityConfigurerAdapter</a> is gone.</li>
<li><a href="https://docs.spring.io/spring-security/reference/5.8/migration/servlet/config.html#use-new-requestmatchers">Ant matchers no longer exist</a>. You have to set a config item to use the previous ant path matching behavior:</li>
</ol>
<pre><code class="language-yaml">spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
</code></pre>
<ol start="3">
<li><a href="https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#migrating-to-spring-security-6">CSRF requires</a> a lot more code to get the default behavior we had before <a href="https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa">in a single page application.</a></li>
</ol>
<pre><code class="language-java">.csrf(c -&gt; c
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()))
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
                
// and CsrfCookieFilter / SpaCsrfTokenRequestHandler classes from the above SPA link
</code></pre>
<ol start="4">
<li>@Component no longer registers API endpoints, you need @RestController or another annotation.</li>
</ol>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[MariaDB4j 3.1.0]]></title><description><![CDATA[<p>I am excited to see MariaDBB4j 3.1.0 <a href="https://github.com/MariaDB4j/MariaDB4j/releases/tag/mariaDB4j-3.1.0">released late last month</a>!</p><p>This is the first official release from the upstream repository that contains <a href="https://github.com/MariaDB4j/MariaDB4j/pull/771">the update to a modern maria</a>!</p><p>If you are working with MariaDB in a Java 17+ toolchain, definitely check this release out!</p>]]></description><link>https://atchison.dev/mariadb4j-3-1-0/</link><guid isPermaLink="false">660db04303267147e74376de</guid><category><![CDATA[MariaDB]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Wed, 03 Apr 2024 19:48:21 GMT</pubDate><content:encoded><![CDATA[<p>I am excited to see MariaDBB4j 3.1.0 <a href="https://github.com/MariaDB4j/MariaDB4j/releases/tag/mariaDB4j-3.1.0">released late last month</a>!</p><p>This is the first official release from the upstream repository that contains <a href="https://github.com/MariaDB4j/MariaDB4j/pull/771">the update to a modern maria</a>!</p><p>If you are working with MariaDB in a Java 17+ toolchain, definitely check this release out!</p>]]></content:encoded></item><item><title><![CDATA[Load Balancing Gunicorn Flask-SocketIO Workers]]></title><description><![CDATA[<p>The Flask ecosystem is atrocious. Coming from a Spring / Java Enterprise background, I did not fully appreciate how many batteries were included.</p><!--kg-card-begin: markdown--><p>I had a web server being served by one <code>geventwebsocket.gunicorn.workers.GeventWebSocketWorker</code>:</p>
<pre><code class="language-bash">gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 --threads 100 -b 0.0.0.0:</code></pre>]]></description><link>https://atchison.dev/load-balancing-gunicorn-flask-socketio-workers/</link><guid isPermaLink="false">6552c3ee54e0af2f581e0c03</guid><category><![CDATA[Python]]></category><category><![CDATA[Flask]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Tue, 14 Nov 2023 16:50:36 GMT</pubDate><content:encoded><![CDATA[<p>The Flask ecosystem is atrocious. Coming from a Spring / Java Enterprise background, I did not fully appreciate how many batteries were included.</p><!--kg-card-begin: markdown--><p>I had a web server being served by one <code>geventwebsocket.gunicorn.workers.GeventWebSocketWorker</code>:</p>
<pre><code class="language-bash">gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 --threads 100 -b 0.0.0.0:5000 'app:create_app()'
</code></pre>
<!--kg-card-end: markdown--><p> This was not keeping up with both the UI and API requests, so I bumped up the worker count:</p><!--kg-card-begin: markdown--><pre><code class="language-bash">gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 5 --threads 100 -b 0.0.0.0:5000 'app:create_app()'
</code></pre>
<!--kg-card-end: markdown--><p>This resulted in web socket connections constantly thrashing because there was no guarantee that the initial handshake worker would get subsequent requests.</p><p>From <a href="https://flask-socketio.readthedocs.io/en/latest/deployment.html">https://flask-socketio.readthedocs.io/en/latest/deployment.html</a> : </p><blockquote>Due to the limited load balancing algorithm used by gunicorn, it is not possible to use more than one worker process when using this web server. For that reason, all the examples above include the <code>-w 1</code> option.</blockquote><blockquote>The workaround to use multiple worker processes with gunicorn is to launch several single-worker instances and put them behind a more capable load balancer such as <a href="https://www.nginx.com/">nginx</a>.</blockquote><p>Gross. Okay.</p><!--kg-card-begin: markdown--><p>Script to stand up multiple app servers:</p>
<pre><code class="language-bash">gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 --threads 100 -b 0.0.0.0:5000 'app:create_app() '&amp;
gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 --threads 100 -b 0.0.0.0:5001 'app:create_app()' &amp;
gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 --threads 100 -b 0.0.0.0:5002 'app:create_app()' &amp;
gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 --threads 100 -b 0.0.0.0:5003 'app:create_app()' &amp;
gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 --threads 100 -b 0.0.0.0:5004 'app:create_app()'
</code></pre>
<p>Nginx has to use sticky sessions when it round robins across these app instances, otherwise websockets will not function correctly. This is achieved by using <code>ip_hash</code> in <code>upstream</code>:</p>
<pre><code class="language-bash">http {

    upstream gunicorns {
        ip_hash;
        server 0.0.0.0:5000;
        server 0.0.0.0:5001;
        server 0.0.0.0:5002;
        server 0.0.0.0:5003;
        server 0.0.0.0:5004;
    }

    server {
        listen 443 ssl default_server;
        root   /usr/share/nginx/html;
        ssl_certificate /path/to/crt;
        ssl_certificate_key /path/to/key;


        location / {
            client_max_body_size 200M;

            auth_basic           &quot;Application Login&quot;;
            auth_basic_user_file /etc/nginx/htpasswd;

            proxy_set_header     X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header     Host $host;

            proxy_pass           http://gunicorns;
        }

        location /socket.io {
            proxy_http_version   1.1;
            proxy_set_header     Upgrade $http_upgrade;
            proxy_set_header     Connection &quot;upgrade&quot;;
            proxy_pass           http://gunicorns;
        }
    }
}
</code></pre>
<p>But wait, there's more!</p>
<p>Flask-SocketIO has to be configured with a message queue:</p>
<pre><code class="language-python">socketio.init_app(app, cors_allowed_origins=&quot;*&quot;, message_queue='redis://')
</code></pre>
<!--kg-card-end: markdown--><p></p>]]></content:encoded></item><item><title><![CDATA[MariaDB4j 2.7.2]]></title><description><![CDATA[<p>I am pleased to announce the <a href="https://github.com/TheKnowles/MariaDB4j/releases/tag/mariaDB4j-2.7.2">release of MariaDB4j 2.7.2</a>.</p><p>Windows support is now fully enabled and tested. </p><p>It also fixes some tmpdir issues for Windows Java 11 users (2.7.x releases) that were fixed in the main upstream branch (Java 17).</p>]]></description><link>https://atchison.dev/mariadb4j-2-7-2/</link><guid isPermaLink="false">6509fa6b7dd7db22b3e4b76d</guid><category><![CDATA[MariaDB]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Tue, 19 Sep 2023 19:48:40 GMT</pubDate><content:encoded><![CDATA[<p>I am pleased to announce the <a href="https://github.com/TheKnowles/MariaDB4j/releases/tag/mariaDB4j-2.7.2">release of MariaDB4j 2.7.2</a>.</p><p>Windows support is now fully enabled and tested. </p><p>It also fixes some tmpdir issues for Windows Java 11 users (2.7.x releases) that were fixed in the main upstream branch (Java 17).</p>]]></content:encoded></item><item><title><![CDATA[mariadb-cdc 1.0.4.2]]></title><description><![CDATA[<p>MariaDB does not come with <a href="https://medium.com/meroxa/stream-your-database-changes-with-change-data-capture-aa8797fa9070">change streams/change data capture</a> out of the box <a href="https://www.mongodb.com/docs/manual/changeStreams/">like mongo</a>. There are some <a href="https://mariadb.com/kb/en/mariadb-maxscale-22-avrorouter-tutorial/">enterprise-y options</a> like MaxScale, but all of these solutions are over engineering for a simple use case of following the <a href="https://mariadb.com/kb/en/binary-log/">binary log</a>. My use case was for MariaDB crud to be</p>]]></description><link>https://atchison.dev/mariadb-cdc-1-0-4-2/</link><guid isPermaLink="false">6477d9e3fc2cac3f37c4920b</guid><category><![CDATA[MariaDB]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Wed, 31 May 2023 23:44:47 GMT</pubDate><content:encoded><![CDATA[<p>MariaDB does not come with <a href="https://medium.com/meroxa/stream-your-database-changes-with-change-data-capture-aa8797fa9070">change streams/change data capture</a> out of the box <a href="https://www.mongodb.com/docs/manual/changeStreams/">like mongo</a>. There are some <a href="https://mariadb.com/kb/en/mariadb-maxscale-22-avrorouter-tutorial/">enterprise-y options</a> like MaxScale, but all of these solutions are over engineering for a simple use case of following the <a href="https://mariadb.com/kb/en/binary-log/">binary log</a>. My use case was for MariaDB crud to be reflected into an Elastic instance. </p><p>I found <a href="https://github.com/madvirus/mariadb-cdc">this library</a> whilst googling around and it is phenomenal. A straightforward library to be able to listen to MariaDB events and perform actions on them. Only issue is that it was only deployed in <a href="https://jitpack.io/">jitpack</a>. Nothing wrong with that per se, but infrastructure wise it is much more likely your organization has a mirror of maven central. </p><p>To that end, I forked this project and put in a couple bug fixes, but most importantly it is now released into maven central:</p><!--kg-card-begin: markdown--><pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;dev.atchison&lt;/groupId&gt;
    &lt;artifactId&gt;mariadb-cdc&lt;/artifactId&gt;
    &lt;version&gt;1.0.4.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<!--kg-card-end: markdown--><p>Release <a href="https://github.com/TheKnowles/mariadb-cdc/releases/tag/1.0.4.2">https://github.com/TheKnowles/mariadb-cdc/releases/tag/1.0.4.2</a></p>]]></content:encoded></item><item><title><![CDATA[Generically Pulling Fields Out of Java Beans]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>You may run across a use case where you do not know exactly what data you are receiving and cannot make a POJO for Jackson to model. In this instance you may want to simply convert all non null properties. We are Jackson-lite in this instance. We can also provide</p>]]></description><link>https://atchison.dev/generically-pulling-fields-out-of-java-beans/</link><guid isPermaLink="false">644c2894fc2cac3f37c49150</guid><category><![CDATA[Java]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Wed, 24 May 2023 13:13:35 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>You may run across a use case where you do not know exactly what data you are receiving and cannot make a POJO for Jackson to model. In this instance you may want to simply convert all non null properties. We are Jackson-lite in this instance. We can also provide a list of fields we know we do not want to convert:</p>
<pre><code class="language-java">  public Map&lt;String, Object&gt; getNonNullProperties(
      final Object objectToIntrospect, List&lt;String&gt; denyListFields) {
    final Map&lt;String, Object&gt; nonNullProperties = new TreeMap&lt;&gt;();
    try {
      final BeanInfo beanInfo = Introspector.getBeanInfo(objectToIntrospect.getClass());
      for (final PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
        try {
          final Object propertyValue = descriptor.getReadMethod().invoke(objectToIntrospect);
          if (propertyValue != null &amp;&amp; !denyListFields.contains(descriptor.getName())) {
            nonNullProperties.put(descriptor.getName(), propertyValue);
          }
        } catch (final IllegalArgumentException
            | IllegalAccessException
            | InvocationTargetException e) {
          // handle
        }
      }
    } catch (final IntrospectionException e) {
      // handle
    }
    return nonNullProperties;
  }
</code></pre>
<p>This is a good stopgap until a more formal ICD is formed or data collapses down into a known entity.</p>
<p><a href="https://stackoverflow.com/questions/2989560/how-to-get-the-fields-in-an-object-via-reflection">Initial inspiration from Stack Overflow.</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[MariaDB4j 2.7.1]]></title><description><![CDATA[<p>One need I had was to have my embedded MariaDB instance write to the <a href="https://mariadb.com/kb/en/binary-log/">binary log</a>, so that I can pick up on events downstream. </p><p>I am pleased to announce <a href="https://github.com/TheKnowles/MariaDB4j/releases/tag/mariaDB4j-2.7.1">MariaDB4j 2.7.1</a> ships with this new functionality. </p><!--kg-card-begin: markdown--><p><a href="https://github.com/TheKnowles/MariaDB4j/blob/release-2.7.x/mariaDB4j-core/src/main/java/ch/vorburger/mariadb4j/DBConfiguration.java">DBConfiguration.java</a> has been updated with a setter for the bin</p>]]></description><link>https://atchison.dev/mariadb4j-2-7-1/</link><guid isPermaLink="false">646d3243fc2cac3f37c491c4</guid><category><![CDATA[MariaDB]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Tue, 23 May 2023 21:46:18 GMT</pubDate><content:encoded><![CDATA[<p>One need I had was to have my embedded MariaDB instance write to the <a href="https://mariadb.com/kb/en/binary-log/">binary log</a>, so that I can pick up on events downstream. </p><p>I am pleased to announce <a href="https://github.com/TheKnowles/MariaDB4j/releases/tag/mariaDB4j-2.7.1">MariaDB4j 2.7.1</a> ships with this new functionality. </p><!--kg-card-begin: markdown--><p><a href="https://github.com/TheKnowles/MariaDB4j/blob/release-2.7.x/mariaDB4j-core/src/main/java/ch/vorburger/mariadb4j/DBConfiguration.java">DBConfiguration.java</a> has been updated with a setter for the bin log and everything is handled behind the scenes. You can have the binary log write events with as little code as</p>
<pre><code class="language-java">  @Bean
  public MariaDB4jSpringService mariaDB4jSpringService() {
    MariaDB4jSpringService mariaDB4jSpringService = new MariaDB4jSpringService();
    DBConfigurationBuilder dbConfigurationBuilder = mariaDB4jSpringService.getConfiguration();
    dbConfigurationBuilder.setBinLogEnabled(true);
    return mariaDB4jSpringService;
  }
</code></pre>
<p>in your application.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[MariaDB4j 2.7.0]]></title><description><![CDATA[<p><a href="https://github.com/MariaDB4j/MariaDB4j">MariaDB4j</a> provides <a href="https://mariadb.org/">mariadb</a> 10.2.11 out of the box. This release of the database is from 2017. Spring boot 2.7.x with Hibernate has more advanced SQL than is supported in 10.2.11.</p><p>I opened a PR against the original project here: <a href="https://github.com/MariaDB4j/MariaDB4j/pull/710">https://github.com/MariaDB4j/MariaDB4j/</a></p>]]></description><link>https://atchison.dev/mariadb4j-2-7-0/</link><guid isPermaLink="false">6466e734fc2cac3f37c4919a</guid><category><![CDATA[MariaDB]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[Knowles Atchison, Jr]]></dc:creator><pubDate>Fri, 19 May 2023 03:17:21 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://github.com/MariaDB4j/MariaDB4j">MariaDB4j</a> provides <a href="https://mariadb.org/">mariadb</a> 10.2.11 out of the box. This release of the database is from 2017. Spring boot 2.7.x with Hibernate has more advanced SQL than is supported in 10.2.11.</p><p>I opened a PR against the original project here: <a href="https://github.com/MariaDB4j/MariaDB4j/pull/710">https://github.com/MariaDB4j/MariaDB4j/pull/710</a> </p><!--kg-card-begin: markdown--><blockquote>
<ul>
<li>MariaDB changed their download urls since the 10.3 commit to [MariaDB4j repo].</li>
<li>MariaDB 10.4 removed no password root user.</li>
<li>MariaDB 10.5.2 onward swapped how they did sym links between mysql* and maria* equivalents.</li>
</ul>
</blockquote>
<!--kg-card-end: markdown--><p>Unfortunately, the main branch has already progressed to spring boot 3 / Java 17 and no maintainence branches will be made. To that end, I have created an effective 2.7.x release branch to support Java 11 / Spring Boot 2.7.x / Spring 5 for the wide swath of existing software that has not or cannot jump to Java 17 / Spring Boot 3. </p><!--kg-card-begin: markdown--><blockquote>
<p>This is a mostly hard fork of MariaDB4j to account for disconnects between Java 11/17 and Spring 5/6.<br>
The last binary update that shipped with MariaDB4j out of the box was 10.2.11, released in 2017.</p>
<p>Using Spring 5/equivalent JPA there are SQL syntax problems that make version 2.6.0 unusable for modern development.<br>
The only current way to get this to work is to use installed binaries on the host system.<br>
This fork aims to create and maintain a 2.7.x release series to account for the above aforementioned issues.</p>
<p>Linux support is tested. Windows support is simulated. OSX support is not provided.<br>
Best effort will be made to maintain upstream updates as applicable.</p>
</blockquote>
<blockquote>
<p>Compatability Chart</p>
<table>
<thead>
<tr>
<th>MariaDB4j</th>
<th>Spring</th>
<th>MariaDB</th>
</tr>
</thead>
<tbody>
<tr>
<td>3.0.0</td>
<td>6</td>
<td>10.2.11 / Installed version</td>
</tr>
<tr>
<td>2.6.0</td>
<td>5</td>
<td>10.2.11 / Installed version</td>
</tr>
<tr>
<td>2.7.x</td>
<td>5</td>
<td>10.6.12+</td>
</tr>
</tbody>
</table>
</blockquote>
<!--kg-card-end: markdown--><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/TheKnowles/MariaDB4j/releases/tag/mariaDB4j-2.7.0"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Release v2.7.0 · TheKnowles/MariaDB4j</div><div class="kg-bookmark-description">2.7.0 This is the initial release in the 2.7.x maintenance branch.
It aims to serve a stopgap for Java 11 projects that need a more modern version of mariadb while using the embedded functionality...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg"><span class="kg-bookmark-author">TheKnowles</span><span class="kg-bookmark-publisher">GitHub</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/105778d4b5199933367e428a4fbcd092fd3c6b293d4c6d6f06cca493831f5a5a/TheKnowles/MariaDB4j/releases/tag/mariaDB4j-2.7.0"></div></a></figure><!--kg-card-begin: markdown--><pre><code class="language-xml">&lt;dependency&gt;
  &lt;groupId&gt;dev.atchison.mariaDB4j&lt;/groupId&gt;
  &lt;artifactId&gt;mariaDB4j&lt;/artifactId&gt;
  &lt;version&gt;2.7.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>