|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | + <head> |
| 4 | + <meta charset="utf-8"> |
| 5 | + <meta content="width=device-width, initial-scale=1" name="viewport"> |
| 6 | + <meta content="Minimum Viable Rex" name="title" property="og:title"> |
| 7 | + <meta content="article" name="type" property="og:type"> |
| 8 | + <meta content="/theme/images/agile_sysadmin.webp" name="type" property="og:image"> |
| 9 | + <meta content="https://blog.ferki.it/2025/03/21/minimum-viable-rex/index.html" name="url" property="og:url"> |
| 10 | + <meta content="Ferenc Erki - agile sysadmin" name="description"> |
| 11 | + <meta content="Ferenc Erki" name="author"> |
| 12 | + <meta content="4KzbYclokErOfwrKiBpX8XCu8ckJ9A7zwueL9VAbAYE" name="google-site-verification"> |
| 13 | + <link href="/theme/images/favicon.svg" rel="icon"> |
| 14 | + <link href="/theme/css/selenized.css" rel="stylesheet"> |
| 15 | + <script data-domain="blog.ferki.it" defer src="https://plausible.io/js/script.js"></script> |
| 16 | + <title>Minimum Viable Rex</title> |
| 17 | + </head> |
| 18 | + <body> |
| 19 | + <header> |
| 20 | + <h1><a href="/">🧑💻 agile sysadmin</a></h1> |
| 21 | + <p>by Ferenc Erki</p> |
| 22 | + </header> |
| 23 | + |
| 24 | + <nav> |
| 25 | + <ul> |
| 26 | + <li><a href="/pages/about.html">About</a></li> |
| 27 | + <li><a href="/">Posts</a></li> |
| 28 | + <li><a href="https://ferki.it" target="_blank">Homepage</a></li> |
| 29 | + <li><a href="https://cal.com/ferki" target="_blank">Booking</a></li> |
| 30 | + </ul> |
| 31 | + </nav> |
| 32 | + |
| 33 | + <main> |
| 34 | + <article> |
| 35 | + |
| 36 | + <header> |
| 37 | + <h1><a href="/2025/03/21/minimum-viable-rex/">Minimum Viable Rex</a></h1> |
| 38 | + |
| 39 | + <aside> |
| 40 | + <time datetime="2025-03-21" title="published"> |
| 41 | + 🗓 2025-03-21 |
| 42 | + </time> |
| 43 | + <a href="/tag/rex/">#rex</a> |
| 44 | + </aside> |
| 45 | + |
| 46 | +</header> |
| 47 | + |
| 48 | + |
| 49 | + <section id="section-1"> |
| 50 | + <p>We consider enabling graceful bootstrapping as one of our main guiding |
| 51 | +principles around <a href="https://metacpan.org/pod/Rex">Rex, the friendly automation |
| 52 | +framework</a>.</p> |
| 53 | + |
| 54 | +<p>While our <a href="https://www.rexify.org/docs/guides/start_using__r__ex.html">How to get started with |
| 55 | +Rex</a> page provides |
| 56 | +a good initial set of concepts, I wondered about the minimal set of features |
| 57 | +that already proves useful in practice. I find this especially interesting when |
| 58 | +using Rex from a cronjob or in a CI/CD pipeline.</p> |
| 59 | + |
| 60 | +<p>Let’s see what I found through this exercise in minimalism.</p> |
| 61 | + |
| 62 | + </section> |
| 63 | + <section id="section-2"> |
| 64 | + <h2 id="basic-architecture"><a href="#basic-architecture">Basic architecture</a></h2> |
| 65 | + |
| 66 | +<p>Rex itself can run where Perl can run, and also can manage hosts both locally |
| 67 | +and over SSH. To differentiate it from managed hosts, we call the host where |
| 68 | +Rex runs as a control host.</p> |
| 69 | + |
| 70 | +<p>Rex code also stays entirely compatible with Perl code, making it extendable to |
| 71 | +support arbitrary use cases, including the use of other protocols when |
| 72 | +required.</p> |
| 73 | + |
| 74 | +<p>Just like the GNU Make tool uses Makefile to describe its actions, Rex uses |
| 75 | +a Rexfile to describe its configuration, inventory, authentication, and tasks.</p> |
| 76 | + |
| 77 | +<p>A task bundles related management steps together, optionally with task-specific |
| 78 | +options, like the default target host for these steps to run on.</p> |
| 79 | + |
| 80 | +<p>It still proves useful to keep those details in mind, though when striving for |
| 81 | +minimalism, it also turns out we may either omit most of them to let the |
| 82 | +built-in logic try likely settings, or just provide enough hints via |
| 83 | +command-line options.</p> |
| 84 | + |
| 85 | +<h2 id="requirements"><a href="#requirements">Requirements</a></h2> |
| 86 | + |
| 87 | +<p>While developing Rex we strive for keeping the requirements low and light.</p> |
| 88 | + |
| 89 | +<p>On the control hosts, Rex requires Perl itself and its dependencies to run. We |
| 90 | +generally aim to support Perl versions up to 10 years. We also consider many |
| 91 | +dependencies optional, especially external tools to streamline some specific |
| 92 | +features like rsync or Augeas.</p> |
| 93 | + |
| 94 | +<p>On the managed hosts, Rex attempts to detect and use Perl and core modules, |
| 95 | +though it can fall back to other methods for the most common situations. In |
| 96 | +case of remotely managed hosts, Rex also expected SSH running there and a valid |
| 97 | +account to log in. For administrative tasks, Rex may use either root directly |
| 98 | +or gain extra privileges with sudo.</p> |
| 99 | + |
| 100 | +<p>Since this post focuses on the minimal approach, in the examples I will use |
| 101 | +Perl-5.40.1 to run Rex-1.16.0 on Gentoo with Net::OpenSSH backend for SSH |
| 102 | +connections, and with SSH configured for key-based authentication for my user.</p> |
| 103 | + |
| 104 | +<h2 id="documentation"><a href="#documentation">Documentation</a></h2> |
| 105 | + |
| 106 | +<p>I find it always nice to keep related documentation at hand.</p> |
| 107 | + |
| 108 | +<p>For Rex, that means starting with the main module documentation via <code>perldoc |
| 109 | +Rex</code> and the main binary documentation via <code>man rex</code> – and both of which also |
| 110 | +points to the available built-in commands via <code>perldoc Rex::Commands</code>.</p> |
| 111 | + |
| 112 | +<p>On top of that, <code>rex -h</code> also shows the built-in help, which I reduced here for |
| 113 | +the minimal set I will cover in this post:</p> |
| 114 | + |
| 115 | +<pre><code class="highlight">$ rex <span class="synSpecial">-h</span> |
| 116 | +usage: |
| 117 | + rex <span class="synOperator">[</span><span class="synConditional"><</span>options<span class="synConditional">></span><span class="synOperator">]</span> <span class="synOperator">[</span><span class="synConditional">-H</span> <span class="synConditional"><</span>host<span class="synConditional">></span><span class="synOperator">]</span> <span class="synOperator">[</span><span class="synConditional">-G</span> <span class="synConditional"><</span>group<span class="synConditional">></span><span class="synOperator">]</span> <span class="synOperator"><</span>task<span class="synOperator">></span> <span class="synOperator">[</span><span class="synConditional"><</span>task-options<span class="synConditional">></span><span class="synOperator">]</span> |
| 118 | + rex <span class="synSpecial">-T</span><span class="synOperator">[</span>m<span class="synOperator">|</span>y<span class="synOperator">|</span>v<span class="synOperator">]</span> <span class="synOperator">[</span><span class="synConditional"><</span>string<span class="synConditional">></span><span class="synOperator">]</span> |
| 119 | + |
| 120 | + <span class="synSpecial">-e</span> Run the given code fragment |
| 121 | + <span class="synSpecial">-H</span> Execute a task on the given hosts <span class="synPreProc">(</span><span class="synSpecial">space delimited</span><span class="synPreProc">)</span> |
| 122 | + |
| 123 | + <span class="synSpecial">-u</span> Username <span class="synStatement">for</span> the ssh connection |
| 124 | + |
| 125 | + <span class="synConditional">-d</span> Show debug output |
| 126 | + <span class="synSpecial">-qw</span> Quiet mode: only output warnings and errors |
| 127 | + |
| 128 | + <span class="synConditional">-h</span> Display this <span class="synStatement">help</span> message |
| 129 | + <span class="synConditional">-v</span> Display <span class="synPreProc">(</span>R<span class="synPreProc">)</span>?ex version |
| 130 | +</code></pre> |
| 131 | + |
| 132 | +<p>Let’s check the current Rex version quickly to make sure we run what we think |
| 133 | +we run:</p> |
| 134 | + |
| 135 | +<pre><code class="highlight">$ rex <span class="synSpecial">-v</span> |
| 136 | +<span class="synPreProc">(</span><span class="synSpecial">R</span><span class="synPreProc">)</span>?ex <span class="synNumber">1</span>.<span class="synNumber">16</span>.<span class="synNumber">0</span> |
| 137 | +</code></pre> |
| 138 | + |
| 139 | +<h2 id="command-line-usage"><a href="#command-line-usage">Command line usage</a></h2> |
| 140 | + |
| 141 | +<p>Similarly to Perl’s <code>-e</code> command line switch to run one-liner programs, Rex |
| 142 | +provides its own <code>-e</code> to run short code fragments.</p> |
| 143 | + |
| 144 | +<p>Let’s print out the standard <code>Hello, World!</code> message with the help of Perl’s |
| 145 | +built-in <code>say()</code> function:</p> |
| 146 | + |
| 147 | +<pre><code class="highlight">$ rex -e 'say "Hello, World!"' |
| 148 | +[2025-03-20 12:46:52] INFO - Running task eval-line on <local> |
| 149 | +Hello, World! |
| 150 | +[2025-03-20 12:46:52] INFO - All tasks successful on all hosts |
| 151 | +</code></pre> |
| 152 | + |
| 153 | +<p>On top of the output we asked for, Rex also shows the <code>INFO</code> log lines in |
| 154 | +green, letting us know that:</p> |
| 155 | + |
| 156 | +<ul> |
| 157 | +<li>it runs an eval-line task on the local host (the default target for tasks)</li> |
| 158 | +<li>everything finished successfully</li> |
| 159 | +</ul> |
| 160 | + |
| 161 | +<p>Some consider that an awful lot of single and double quotes next to each other, |
| 162 | +though. Since the argument of <code>rex -e</code> takes any Perl code, let’s use the <code>q()</code> |
| 163 | +quote-like operator instead:</p> |
| 164 | + |
| 165 | +<pre><code class="highlight">$ rex -e 'say q(Hello, World!)' |
| 166 | +[2025-03-20 12:53:41] INFO - Running task eval-line on <local> |
| 167 | +Hello, World! |
| 168 | +[2025-03-20 12:53:42] INFO - All tasks successful on all hosts |
| 169 | +</code></pre> |
| 170 | + |
| 171 | +<p>Same result, though the code may feel easier to work with. For brevity, I will |
| 172 | +also exclude some of those logs in the further examples. The help shows a few |
| 173 | +options to hide the logs and I tend to use <code>-qw</code> to still keep any warnings and |
| 174 | +errors visible.</p> |
| 175 | + |
| 176 | +<pre><code class="highlight">$ rex -qw -e 'say q(Hello, World!)' |
| 177 | +Hello, World! |
| 178 | +</code></pre> |
| 179 | + |
| 180 | +<h2 id="run-commands-and-manage-files"><a href="#run-commands-and-manage-files">Run commands and manage files</a></h2> |
| 181 | + |
| 182 | +<p>While printing messages sounds interesting, we consider the core capabilities |
| 183 | +of Rex as running commands and managing files.</p> |
| 184 | + |
| 185 | +<p>For example, the <code>hostname</code> command on Linux prints the hostname, and Rex |
| 186 | +provides the <code>run()</code> command to, well, run commands:</p> |
| 187 | + |
| 188 | +<pre><code class="highlight">$ rex -qw -e 'say run q(hostname)' |
| 189 | +mymachine |
| 190 | +</code></pre> |
| 191 | + |
| 192 | +<p>The <code>file()</code> command of Rex helps manage files:</p> |
| 193 | + |
| 194 | +<pre><code class="highlight">$ rex -qw -e 'file "/tmp/hello", content => q(Hello, World!)' |
| 195 | +</code></pre> |
| 196 | + |
| 197 | +<p>Hmm, no output, though no signs of any warnings or errors. I’d like to check if |
| 198 | +the <code>/tmp/hello</code> file does indeed contain <code>Hello, World!</code> in it. On Linux, |
| 199 | +I would use the <code>cat</code> command for that, and Rex provides the same functionality |
| 200 | +under the same name:</p> |
| 201 | + |
| 202 | +<pre><code class="highlight">$ rex -qw -e 'say cat q(/tmp/hello)' |
| 203 | +Hello, World! |
| 204 | +</code></pre> |
| 205 | + |
| 206 | +<p>Why does Rex duplicate the <code>cat</code> command, when we would also call <code>run q(cat |
| 207 | +/tmp/hello)</code>? Because we can only expect <code>cat</code>, the Linux command, to exist |
| 208 | +only on Linux – while Rex may have to manage other operating systems too, where |
| 209 | +the system does not have <code>cat</code>. On those systems Rex abstracts away the |
| 210 | +difference, and uses other methods to achieve the same results.</p> |
| 211 | + |
| 212 | +<h2 id="manage-remote-hosts"><a href="#manage-remote-hosts">Manage remote hosts</a></h2> |
| 213 | + |
| 214 | +<p>While executing ad-hoc tasks on the local machine sounds useful, many use cases |
| 215 | +involve running tasks remotely. Let’s pass a host to connect to via SSH with |
| 216 | +the <code>-H</code> option:</p> |
| 217 | + |
| 218 | +<pre><code class="highlight">$ rex -H myserver -e 'say run q(hostname)' |
| 219 | +[2025-03-20 19:49:16] INFO - Running task eval-line on myserver |
| 220 | +myserver |
| 221 | +[2025-03-20 19:49:19] INFO - All tasks successful on all hosts |
| 222 | +</code></pre> |
| 223 | + |
| 224 | +<p>Note that the <code>INFO</code> log output now shows running the task on <code>myserver</code>, and |
| 225 | +the output of the <code>hostname</code> command changed accordingly – it did run on |
| 226 | +<code>myserver</code> after all.</p> |
| 227 | + |
| 228 | +<p>Also note that Rex discovered the authentication details it may use to log in |
| 229 | +to <code>myserver</code>. It can parse (a subset of<a class="footnote" href="#fn:ssh_config" id="fnref:ssh_config">1</a>) the SSH configuration in |
| 230 | +<code>~/.ssh/config</code>, and can also fall back to use the current local username to |
| 231 | +log in. In case we need to, we can enforce a specific user, like <code>myuser</code>, with |
| 232 | +<code>-u</code>:</p> |
| 233 | + |
| 234 | +<pre><code class="highlight">$ rex -qw -H myserver -u myuser -e 'say run q(hostname)' |
| 235 | +myserver |
| 236 | +</code></pre> |
| 237 | + |
| 238 | +<p>Since I use the Net::OpenSSH backend, it can pick up and use my SSH keys too. |
| 239 | +In case it needs to fall back to password-based authentication, or the key |
| 240 | +needs a passphrase, it will prompt for it.</p> |
| 241 | + |
| 242 | +<h2 id="debug-output"><a href="#debug-output">Debug output</a></h2> |
| 243 | + |
| 244 | +<p>These one-liner tasks may prove difficult to debug, and the built-in debug |
| 245 | +output via <code>-d</code> often helps a lot to understand what happens from the |
| 246 | +perspective of Rex.</p> |
| 247 | + |
| 248 | +<pre><code class="highlight">$ rex -d -e 'say run q(hostname)' |
| 249 | +</code></pre> |
| 250 | + |
| 251 | +<p>I omit the full output here as it can get verbose, though it will contain |
| 252 | +information about the following:</p> |
| 253 | + |
| 254 | +<ul> |
| 255 | +<li>the running Rex version</li> |
| 256 | +<li>list of command line parameters</li> |
| 257 | +<li>internal details of the whole process, including the picked up configuration, |
| 258 | +and dependencies</li> |
| 259 | +<li>authentication information used during execution</li> |
| 260 | +<li>task execution details</li> |
| 261 | +<li>list of shutdown steps</li> |
| 262 | +</ul> |
| 263 | + |
| 264 | +<h2 id="summary"><a href="#summary">Summary</a></h2> |
| 265 | + |
| 266 | +<p>For a frugal introduction to Rex, one may find the <code>-e</code> command line option |
| 267 | +already enough to execute ad-hoc tasks locally, and perhaps <code>-H</code> to execute the |
| 268 | +same remotely, along with how to find further information in the documentation.</p> |
| 269 | + |
| 270 | +<p>Adding a few more options to the repertoire, like overriding authentication |
| 271 | +details, and controlling the verbosity of the output, makes the first |
| 272 | +experience with Rex viable for the most common practical uses.</p> |
| 273 | + |
| 274 | +<div class="footnotes"> |
| 275 | +<hr> |
| 276 | +<ol> |
| 277 | + |
| 278 | +<li id="fn:ssh_config"><p>I work on fully transparent support of arbitrary SSH |
| 279 | +configuration<a class="reversefootnote" href="#fnref:ssh_config"> ↩</a></p></li> |
| 280 | + |
| 281 | +</ol> |
| 282 | +</div> |
| 283 | + |
| 284 | + </section> |
| 285 | + |
| 286 | +</article> |
| 287 | + |
| 288 | +<ul class="pager"> |
| 289 | + <li class="prev"> |
| 290 | + <a href="/2025/03/12/perl-basics-for-rex/index.html" rel="prev"> |
| 291 | + ← Older |
| 292 | + </a> |
| 293 | + </li> |
| 294 | + <li class="next"> |
| 295 | + </li> |
| 296 | +</ul> |
| 297 | + |
| 298 | + |
| 299 | + </main> |
| 300 | + |
| 301 | + <footer> |
| 302 | + |
| 303 | + <ul> |
| 304 | + <li><a href="https://blog.ferki.it/index.rss" target="_blank">RSS</a></li> |
| 305 | + <li><a href="https://blog.ferki.it/index.atom" target="_blank">Atom</a></li> |
| 306 | + <li><a href="https://github.com/ferki" rel="me" target="_blank">GitHub</a></li> |
| 307 | + <li><a href="https://profile.codersrank.io/user/ferki" rel="me" target="_blank">CodersRank</a></li> |
| 308 | + <li><a href="https://www.linkedin.com/in/ferki" rel="me" target="_blank">LinkedIn</a></li> |
| 309 | + <li><a href="https://fosstodon.org/@ferki" rel="me" target="_blank">Mastodon</a></li> |
| 310 | + <li><a href="mailto:ferki@ferki.it">Email</a></li> |
| 311 | + <li><a href="/pages/impressum.html">Impressum</a></li> |
| 312 | + <li><a href="/pages/privacy_policy.html">Privacy policy</a></li> |
| 313 | + </ul> |
| 314 | + <ul> |
| 315 | + <li>© 2023–2025 Ferenc Erki</li> |
| 316 | + </ul> |
| 317 | + |
| 318 | + </footer> |
| 319 | + </body> |
| 320 | +</html> |
0 commit comments