<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Applications on Digi Hunch</title><link>https://static.digihunch.com/tag/applications/</link><description>Recent content in Applications on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 12:53:21 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/applications/index.xml" rel="self" type="application/rss+xml"/><item><title>Key mapping for external PC keyboard on Mac</title><link>https://static.digihunch.com/2022/11/key-mapping-on-external-pc-keyboard-on-macbook/</link><pubDate>Wed, 23 Nov 2022 10:38:00 -0400</pubDate><guid>https://static.digihunch.com/2022/11/key-mapping-on-external-pc-keyboard-on-macbook/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/key-mapping-image.webp" alt="Featured image of post Key mapping for external PC keyboard on Mac" /&gt;&lt;p class="wp-block-paragraph"&gt;This post is to document my steps to use external Windows keyboard on Mac with custom key mapping.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-background"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apple&amp;#8217;s magic keyboard does not support multi-device so I have to repurpose my Logitech K810 keyboard with MacBook. Logitech K810 is and &lt;a href="https://www.pcmag.com/reviews/logitech-k810-bluetooth-illuminated-keyboard"&gt;old model&lt;/a&gt; with Windows key layout even though it also supports MacOS.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The bottom row has the same number of keys as magic keyboard but with different keys. For example, magic keyboard has cmd key (aka GUI keys) besides space bar but K810 has Alt. Magic keyboard has Option key next to cmd key and K810 has Windows key on the left and Ctrl key on the right. The position of left Ctrl and FN is different on magic keyboard too. Since I rely on &lt;a href="https://static.digihunch.com/2018/02/linux-tips-and-tricks-in-shell/"&gt;shortcuts&lt;/a&gt; on Magic keyboard, I therefore decide configure key mapping on the Windows key, Alt keys and right ctrl key to match magic keyboard. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="450" src="https://static.digihunch.com/wp-content/uploads/2025/04/K810.webp" alt="" class="wp-image-12872" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/K810.webp 1024w, https://static.digihunch.com/wp-content/uploads/2025/04/K810-300x132.webp 300w, https://static.digihunch.com/wp-content/uploads/2025/04/K810-768x338.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;K810 Layout&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a nutshell, I need to remap some keys on an external Windows keyboard connected to MacOS (Monterey).&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;What is not working&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In MacOS, you could modify keys from keyboard preference as below. However, in my case I need to keep left ctrl key and overwrite the right ctrl key. MacOS does not distinguish them unfortunately. The native method is not flexible so I did not bother.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="300" height="206" src="https://static.digihunch.com/wp-content/uploads/2025/04/macos-keyboard-pref.webp" alt="" class="wp-image-12871"/&gt;&lt;figcaption class="wp-element-caption"&gt;Use MacOS keyboard preference to modify keys&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the mean time, I hate to install a third-party application (such as &lt;a href="https://karabiner-elements.pqrs.org/"&gt;Karabiner-Elements&lt;/a&gt;) just for the purpose of key mapping. Even though I appreciate the efforts by the community, I just don&amp;#8217;t find it a neat solution for the additional dependency.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Challenges with key mapping&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I then came across a developer&amp;#8217;s &lt;a href="https://rakhesh.com/mac/using-hidutil-to-map-macos-keyboard-keys/"&gt;post&lt;/a&gt; on using a utility called hidutil to map keys. This is promising after I tested using this command to swap key a/A and b/B. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hidutil property --set &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;UserKeyMapping&amp;#34;:[{&amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;: 0x700000004,&amp;#34;HIDKeyboardModifierMappingDst&amp;#34;: 0x700000005},{&amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;: 0x700000005,&amp;#34;HIDKeyboardModifierMappingDst&amp;#34;: 0x700000004}]}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above takes effect immediately, on all keyboards. In my case I only need mapping on an external keyboard, not the built-in keyboard. Luckily hidutil has &amp;#8211;matching switch where I can specify ProductID and VendorID. I can find both of them from About This Mac -&amp;gt; System Report -&amp;gt; Hardware -&amp;gt; Bluetooth:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="654" src="https://static.digihunch.com/wp-content/uploads/2025/04/vendor-id-product-id.webp" alt="" class="wp-image-12870" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/vendor-id-product-id.webp 1024w, https://static.digihunch.com/wp-content/uploads/2025/04/vendor-id-product-id-300x192.webp 300w, https://static.digihunch.com/wp-content/uploads/2025/04/vendor-id-product-id-768x491.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Vendor ID and Product ID&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From &lt;a href="https://developer.apple.com/library/archive/technotes/tn2450/_index.html"&gt;this&lt;/a&gt; technical note, we can find out the usage IDs of the keys in the table at the bottom of the page. Then use the Usage ID as the last two bytes of the source and destination values of the mapping expression above. For example, 0x700000004 stands for key A/a, and 0x700000005 stands for key B/b. In my case, after going through this table, my key mapping is:&lt;/p&gt;&#10;&lt;figure class="wp-block-table"&gt;&lt;table class="has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Source Key on K810&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Source Code on K810&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Destination Key&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Destination Code&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Left Alt&lt;/td&gt;&lt;td&gt;0x7000000E2&lt;/td&gt;&lt;td&gt;Left GUI (cmd)&lt;/td&gt;&lt;td&gt;0x7000000E3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Right Alt&lt;/td&gt;&lt;td&gt;0x7000000E6&lt;/td&gt;&lt;td&gt;Right GUI (cmd)&lt;/td&gt;&lt;td&gt;0x7000000E7&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Left Win&lt;/td&gt;&lt;td&gt;0x7000000E3&lt;/td&gt;&lt;td&gt;Left Alt (option)&lt;/td&gt;&lt;td&gt;0x7000000E2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Right Ctrl&lt;/td&gt;&lt;td&gt;0x7000000E4&lt;/td&gt;&lt;td&gt;Right Alt (option)&lt;/td&gt;&lt;td&gt;0x7000000E6&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fn+F12&lt;/td&gt;&lt;td&gt;0x700000045&lt;/td&gt;&lt;td&gt;Globe&lt;/td&gt;&lt;td&gt;0xFF00000003&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is even a &lt;a href="https://hidutil-generator.netlify.app/"&gt;website&lt;/a&gt; that helps you generate the usage ID. I put together a command, which is working, on the external keyboard.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hidutil property --matching &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;ProductID&amp;#34;:0xB319,&amp;#34;VendorID&amp;#34;:0x046D}&amp;#39;&lt;/span&gt; --set &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;UserKeyMapping&amp;#34;:[{&amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E2,&amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E3},{&amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E6,&amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E7},&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;{&amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E3,&amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E2},&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;{&amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E4,&amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E6},&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;{&amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x700000045,&amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0xFF00000003}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;]}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The other way to confirm that key mapping is working, is to look up key mapping with the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hidutil property --matching &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;ProductID&amp;#34;:0xB319,&amp;#34;VendorID&amp;#34;:0x046D}&amp;#39;&lt;/span&gt; --get &lt;span style="color:#e6db74"&gt;&amp;#34;UserKeyMapping&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To wipe out the mapping, we can use the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hidutil property --matching &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;ProductID&amp;#34;:0xB319,&amp;#34;VendorID&amp;#34;:0x046D}&amp;#39;&lt;/span&gt; --set &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;UserKeyMapping&amp;#34;:[]}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that we have to use the same matching expression. However, I noticed that the command only works while the bluetooth keyboard is connected. When I reboot the MacBook the mapping goes away as well.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;The solution to key mapping&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Even though I could run this command after reboot using a plist file in ~/Library/LaunchAgents/, as the reference blog suggests, it will not work because I cannot guarantee the bluetooth keyboard is connected before MacOS executes it. Also, I might disconnect and re-connect the keyboard and I want to ensure that the system runs that command whenever the keyboard gets connected.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &lt;a href="https://github.com/pqrs-org/Karabiner-Elements"&gt;Karabiner Element&lt;/a&gt; community, some developers touched on &lt;a href="https://github.com/pqrs-org/Karabiner-Elements/issues/2196#issuecomment-621928482"&gt;this&lt;/a&gt;. Basically we can customize the plist file to tell MacOS when to fire the hidutil command, by using &amp;#8220;LaunchEvents&amp;#8221; key in the XML. There is an &lt;a href="https://github.com/pqrs-org/Karabiner-Elements/issues/2196#issuecomment-621928482"&gt;example&lt;/a&gt; for USB keyboard. For myself, I edit the plist file to be:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;!DOCTYPE plist PUBLIC &amp;#34;-//Apple//DTD PLIST 1.0//EN&amp;#34; &amp;#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd&amp;#34;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;plist&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;local.hidutilKeyMapping&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;LaunchEvents&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.iokit.matching&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.bluetooth.hostController&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;IOProviderClass&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;IOBluetoothHCIController&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;idProduct&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;B319&lt;span style="color:#f92672"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;idVendor&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;046D&lt;span style="color:#f92672"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;IOMatchLaunchStream&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/usr/bin/hidutil&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;property&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;--matching&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;{&amp;#34;ProductID&amp;#34;:0xB319,&amp;#34;VendorID&amp;#34;:0x046D}&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;--set&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;UserKeyMapping&amp;#34;: [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E2,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E6,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E3,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x7000000E4,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0x7000000E6&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingSrc&amp;#34;:0x700000045,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;HIDKeyboardModifierMappingDst&amp;#34;:0xFF00000003&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&lt;span style="color:#f92672"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RunAtLoad&lt;span style="color:#f92672"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Store the file above as ~/Library/LaunchAgents/com.local.hidutilKeyMapping.plist (create LaunchAgents directory if it doesn&amp;#8217;t exist) and then run the following to load it:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;launchctl load ~/Library/LaunchAgents/com.local.hidutilKeyMapping.plist&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This works perfectly. The dict section (line 7 to line 23) of the file is based on &lt;a href="http://technologeeks.com/docs/launchd.pdf"&gt;this&lt;/a&gt; document as the GitHub &lt;a href="https://github.com/pqrs-org/Karabiner-Elements/issues/2196#issuecomment-621928482"&gt;comment&lt;/a&gt; suggests. The LaunchEvents section narrows down the type of events that will trigger the program with arguments. It triggers whenever a bluetooth device with specific ProductID and VendorID connects to Mac. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can still use hidutil command from the previous section to ensure that the custom key mapping remain in effect:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="531" src="https://static.digihunch.com/wp-content/uploads/2025/04/hidutil-command-1024x531.webp" alt="" class="wp-image-12869" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/hidutil-command-1024x531.webp 1024w, https://static.digihunch.com/wp-content/uploads/2025/04/hidutil-command-300x155.webp 300w, https://static.digihunch.com/wp-content/uploads/2025/04/hidutil-command-768x398.webp 768w, https://static.digihunch.com/wp-content/uploads/2025/04/hidutil-command-1536x796.webp 1536w, https://static.digihunch.com/wp-content/uploads/2025/04/hidutil-command-2048x1061.webp 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that the output above has usage values converted to decimal from hexadecimal, with destination on top and source at the bottom.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I like this solution as it is light-weight, targeting a specific event, highly customizable and resolves an issue in a very specific circumstance.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Final words&lt;/h2&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="91" src="https://static.digihunch.com/wp-content/uploads/2025/04/K380-layout.webp" alt="" class="wp-image-12868" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/K380-layout.webp 1024w, https://static.digihunch.com/wp-content/uploads/2025/04/K380-layout-300x27.webp 300w, https://static.digihunch.com/wp-content/uploads/2025/04/K380-layout-768x68.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;K380 layout&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After all this customization effort, I came across the Logitech &lt;a href="https://www.logitech.com/en-us/products/keyboards/k380-multi-device.920-011135.html"&gt;K380&lt;/a&gt; model (current alternative to K810), whose layout is exactly the way I customized K810 to be. So I ordered the new K380 model for just $29.99 during the week of black Friday. Remembering that I had my K810 for $94.98 back in 2015, what&amp;#8217;s the point of all this&amp;#8230;.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/11/aws-serverless-services-and-developer-tools/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AWS serverless services and developer tools&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/12/landing-zone-in-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Landing Zone in AWS – An Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kick the tires on ArgoCD</title><link>https://static.digihunch.com/2022/07/kick-the-tires-on-argocd/</link><pubDate>Sun, 10 Jul 2022 00:10:00 -0400</pubDate><guid>https://static.digihunch.com/2022/07/kick-the-tires-on-argocd/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-argocd-tire.webp" alt="Featured image of post Kick the tires on ArgoCD" /&gt;&lt;h2 class="wp-block-heading" id="h-background"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In January, I wrote about &lt;a href="https://static.digihunch.com/2022/01/fluxcd-continuous-deployment-with-gitops/"&gt;FluxCD&lt;/a&gt;, and adopted it in the &lt;a href="https://github.com/digihunch/korthweb"&gt;Korthweb&lt;/a&gt; project. I like the simple design of FluxCD, and I am comfortable with commands without using a web UI. Half a year later, I am re-visiting this choice, with ArgoCD in mind.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After reading numerous recent posts that compare the two (such as this &lt;a href="https://thenewstack.io/gitops-on-kubernetes-deciding-between-argo-cd-and-flux"&gt;one&lt;/a&gt; from the new stack), I started to give more thoughts on ArgoCD, for a couple reasons. First, ArgoCD has more contributing companies and public references. Red Hat adopted Argo CD as the underlying technology for &lt;a href="https://docs.openshift.com/container-platform/4.7/cicd/gitops/understanding-openshift-gitops.html#about-redhat-openshift-gitops_understanding-openshift-gitops"&gt;OpenShift GitOps&lt;/a&gt; (and Tekton for pipeline). Second, ArgoCD has a mature UI. In corporate collaboration, especially with those not well-versed with command line, a UI is extremely helpful. Bundled with the UI, Argo CD also uses its own RBAC independent of Kubernetes RBAC, making it a great choice for continuous deployment for enterprise applications.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;User Interface&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can choose to install just the &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/installation/#core"&gt;core&lt;/a&gt; components, without UI, SSO and multi-cluster features. To me, it does not make sense because those features are exactly the reason to choose Argo CD. Argo CD also has an eponymous CLI tool, similar to flux for Flux CD. Since Argo CD uses its own RBAC independent of Kubernetes RBAC, Argo CD will have its own credential. The default username is admin and password needs to be retrieved from Secrets. In the tutorial we use this user&amp;#8217;s credential to interact with the target cluster. This is different from Flux where the client simply uses kubectl configuration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &amp;#8220;&lt;a href="https://argo-cd.readthedocs.io/en/latest/getting_started/#1-install-argo-cd"&gt;Getting Started&lt;/a&gt;&amp;#8221; guide, we create a namespace &amp;#8220;argocd&amp;#8221; and install it with &lt;a href="https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml"&gt;manifests&lt;/a&gt;. Alternatively, we can use &lt;a href="https://argo-cd.readthedocs.io/en/stable/cli_installation/"&gt;CLI tool&lt;/a&gt; to install it. With Argo CD, we can consider hosting it on a different port than the workload&amp;#8217;s port, such as 8443. This port serves as &amp;#8220;GitOps management port&amp;#8221; separated from the application port (e.g. 443) for business workload. Just like any application on Kubernetes, we usually need to configure an Ingress on the management port.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the argocd namespace, we can see a few &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/server-commands/argocd-server/"&gt;services&lt;/a&gt;. The application set controller acts as the main controller that reconciles between actual state and desired state. There is also a redis server, for storing data in Argo CD. Another service to note is the &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/server-commands/argocd-dex/"&gt;dex&lt;/a&gt; server, indicating that the SSO capability is provided by the &lt;a href="https://dexidp.io/docs/kubernetes/"&gt;Dex&lt;/a&gt; project.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Features&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In FluxCD, they use a CRD Kustomization with kustomize.toolkit.fluxcd.io/v1beta2 as version. This name is confusing. Luckily, in ArgoCD, the CRDs are Application (with argoproj.io/v1alpha1 version) and ApplicationSet (with argoproj.io/v1alpha1 version). The controllers monitors CRs created with these CRDs. ArgoCD defines Application as a group of Kubernetes resources as defined by a manifest. Argo CD Application resource deploys resources from a single Git repository to a single destination cluster/namespace. On the other hand, &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/"&gt;ApplicationSet&lt;/a&gt; uses templated automation to create, modify, and manage multiple Argo CD applications at once. ApplicationSet controllers monitors ApplicationSet resources. We can define a template within the declaration of an &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/#the-applicationset-resource"&gt;ApplicationSet&lt;/a&gt; and it allows for parameter substitution.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another key capability for a GitOps utility is the support of multiple templating tools. FluxCD supports Helm and &lt;a href="https://kustomize.io/"&gt;Kustomize&lt;/a&gt;. So do Argo CD. In addition, ArgoCD also supports &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/jsonnet/"&gt;jsonnet&lt;/a&gt; (&lt;a href="https://argo-cd.readthedocs.io/en/release-2.3/user-guide/ksonnet/"&gt;ksonnet&lt;/a&gt; is not supported anymore). Jsonnet is a templating tool with many useful operators. Kubectl can directly consume Jsonnet&amp;#8217;s JSON output as if it were YAML. In FluxCD, there are different CRDs for Helm and Kustomize (HelmRelease and Kustomization). In ArgoCD, the Application CRD covers &lt;a href="https://argo-cd.readthedocs.io/en/latest/user-guide/helm/#declarative"&gt;Helm&lt;/a&gt;, &lt;a href="https://argo-cd.readthedocs.io/en/latest/user-guide/kustomize/"&gt;Kustomize&lt;/a&gt;, and &lt;a href="https://argo-cd.readthedocs.io/en/latest/user-guide/jsonnet/"&gt;Jsonnet&lt;/a&gt; by embedding them as attributes in the declaration. This makes it even simpler than Flux CD. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Secret Management&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It&amp;#8217;s a big no-no to put secret in a code repository. Therefore any GitOps solution must solve the problem with secret logistics. What baffles me is &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/secret-management/"&gt;ArgoCD remains un-opinionated&lt;/a&gt; on this matter. There appears to be some &lt;a href="https://github.com/argoproj/argo-cd/issues/1364"&gt;context&lt;/a&gt; for this stance. As a result, ArgoCD only points to a few third-party secret management solutions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Secret logistics is a tricky problem with GitOps workflow. One of the techniques is &lt;a href="https://sealed-secrets.netlify.app/"&gt;sealed secret&lt;/a&gt;. In the repo we store the secret as encrypted by public key. The controller keeps the private key which decrypts the secret at the time of deployment. A few engineers don&amp;#8217;t find it feasible for operation, as explained in &lt;a href="https://betterprogramming.pub/why-you-should-avoid-sealed-secrets-in-your-gitops-deployment-e50131d360dd"&gt;this&lt;/a&gt; post and &lt;a href="https://itnext.io/goodbye-sealed-secrets-hello-sops-3ee6a92662bb"&gt;this&lt;/a&gt; post.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;An alternative is to use &lt;a href="https://external-secrets.io/latest"&gt;external secret operator&lt;/a&gt;. Such operators will be able to sync a secret from external secret store such as Hashicorp Vault, AWS secret manager, Azure Key Vault, etc. This is much neater because we only store reference to secret in the repository. However, when the cluster assesses external secret store, it still requires either a secret, or a permission. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another technique that drawing attentions is using &lt;a href="https://github.com/mozilla/sops#encrypting-using-azure-key-vault"&gt;sops&lt;/a&gt; for secret and &lt;a href="https://github.com/viaduct-ai/kustomize-sops#argo-cd-integration"&gt;kustomize-sops&lt;/a&gt; to integrate with ArgoCD. Mozilla&amp;#8217;s &lt;a href="https://github.com/mozilla/sops#encrypting-using-azure-key-vault"&gt;sops&lt;/a&gt; (SecretOPerationS) project releases a binary utility called sops to encrypt and decrypt YAML manifest. It can encrypt only the values in YAML manifest and not the attributes (keys). We use sops to encrypt YAML files in order to store them in Git repo. We also use sops to decrypt YAML right before we deploy it with kubectl. The key pair for encryption and decryption can be stored in Azure Key Vault, AWS KMS, GCP KMS, and even &lt;a href="https://github.com/FiloSottile/age"&gt;age&lt;/a&gt; and pgp. &lt;a href="https://www.thorsten-hans.com/encrypt-your-kubernetes-secrets-with-mozilla-sops"&gt;Here&lt;/a&gt; is a simple tutorial. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Example&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a few command we can configure a minimal example. I first use KinD or Minikube to create a cluster following the steps in &lt;a href="https://github.com/digihunch/real-quicK-cluster"&gt;real-quicK-cluster&lt;/a&gt; repo. Then we configure an application using YAML manifest.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl create namespace argocd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;{.data.password}&amp;#34;&lt;/span&gt; | base64 -d; echo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl port-forward svc/argocd-server -n argocd 8443:443&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From MacOS, I can now browse to https://localhost:8443/ for ArgoCD UI, and login with admin user and the password as printed above. We can then use the UI to create an Application. Alternatively, we can configure an application using CLI utility:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ brew install argocd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ argocd login localhost:8443&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-server https://kubernetes.default.svc --dest-namespace default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ argocd app sync guestbook&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;On the GUI we should see the application is sync&amp;#8217;ed. &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="840" height="671" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-8.png" alt="" class="wp-image-5452"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the left panel, click on the gear icon for settings. Under Clusters, we can see the cluster URL, which we use as dest-server value above. We can add more clusters here. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Pick the right open-source project&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;My experience with ArgoCD and FluxCD raised an issue to reflect on. For the same problem, there are multiple choices of open-source project. What would be a good methodology to choose the right open-source technology? I would first look at the following factors:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;History&lt;/li&gt;&#10;&lt;li&gt;Community size&lt;/li&gt;&#10;&lt;li&gt;License model&lt;/li&gt;&#10;&lt;li&gt;Enterprise support&lt;/li&gt;&#10;&lt;li&gt;Governing model&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The history and community size give a good idea of the technical maturity level. The License model has legal implications as to where you can use the technology. License model can change even after the project has been launched and even become not open-source any more. One example is &lt;a href="https://www.elastic.co/blog/why-license-change-aws"&gt;Elasticsearch license change&lt;/a&gt; in Jan 2021, from the very open &lt;a href="https://www.apache.org/licenses/LICENSE-2.0"&gt;APLv2&lt;/a&gt; to under dual license of both &lt;a href="https://www.elastic.co/licensing/elastic-license"&gt;Elastic License&lt;/a&gt; (by Elastic) and &lt;a href="https://en.wikipedia.org/wiki/Server_Side_Public_License"&gt;Server Side Public License&lt;/a&gt; (SSPL, introduced by MongoDB). Neither license is certified by &lt;a href="https://opensource.org/licenses"&gt;OSI&lt;/a&gt; as compliant with &lt;a href="https://en.wikipedia.org/wiki/The_Open_Source_Definition"&gt;open source definition&lt;/a&gt;. For enterprises, apart from licensing, I&amp;#8217;d also consider if any organization provides commercial support for the open-source product. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;What is often neglected is the governing model. The governing model determines how product decisions are made that will shape the future of the project. For example, CNCF is a vendor neutral foundation for cloud computing and if an organization contributes a project to CNCF, then the member projects have to align with CNCF&amp;#8217;s governing practices. Google also introduced an &lt;a href="https://www.infoworld.com/article/3566274/what-is-googles-open-usage-commons-and-why.html"&gt;OUC&lt;/a&gt; (Open Usage Common) governing model.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another good way is to just follow Red Hat&amp;#8217;s choice. Red Hat built its business model around open-source technologies. They have to pick open-source project to provide enterprise level support so presumably they have a rigour selection process that one can piggyback off. Products supported by Red Hat are composed of open source components often vetted by multiple upstream communities, and changes made to these components are pushed to their respective upstream projects, often before they land in a supported product from Red Hat. &lt;a href="https://cs.stanford.edu/people/eroberts/cs201/projects/open-source/econ.htm"&gt;Here&lt;/a&gt; are more insights in this business model.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Had I gone with the Red Hat rule, I would have picked Argo CD in the first place because that is the base of &lt;a href="https://docs.openshift.com/container-platform/4.7/cicd/gitops/understanding-openshift-gitops.html"&gt;OpenShift GitOps&lt;/a&gt;! &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Verdict&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ArgoCD and FluxCD are tools for GitOps. However, just using a GitOps tool does not guarantee that all workload are deployed continuously. First, these GitOps controllers cannot replace &lt;a href="https://static.digihunch.com/2022/04/kubernetes-operator/"&gt;Operators&lt;/a&gt;, which has domain knowledge about how to orchestrate their workloads. Second, resources not directly managed by these operators (such as those installed by Helm) are not being monitored continuously. For continuous deployment over all workloads, I recommend use operator to install third party applications, and use Argo CD or Flux CD to manage the operators. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;GitOps makes use of the controller pattern to manage deployment in a continuous matter. Controller works closely with CRDs. ArgoCD uses similar set of CRDs to manage continuous deployment. What sets it apart is the user friendly web UI, the IAM integration and multi-cluster capabilities. These capabilities make ArgoCD well adapted in enterprise IT eco-system.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/06/chaos-mesh-cloud-native-chaos-engineering/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Chaos Mesh – Cloud Native Chaos Engineering&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/07/intro-to-ceph-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to Ceph storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>