<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joe Neville</title>
    <description>The latest articles on DEV Community by Joe Neville (@joeneville_).</description>
    <link>https://dev.to/joeneville_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F411320%2Fb15f32ad-7d97-49f1-8492-fa2716487909.jpg</url>
      <title>DEV Community: Joe Neville</title>
      <link>https://dev.to/joeneville_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joeneville_"/>
    <language>en</language>
    <item>
      <title>Rust reqwest and Aruba AOS-CX API</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Fri, 12 Aug 2022 19:05:00 +0000</pubDate>
      <link>https://dev.to/joeneville_/rust-reqwests-and-aruba-aos-cx-api-2435</link>
      <guid>https://dev.to/joeneville_/rust-reqwests-and-aruba-aos-cx-api-2435</guid>
      <description>&lt;h1&gt;
  
  
  AOS-CX rust reqwest client
&lt;/h1&gt;

&lt;p&gt;These code snippets provide an example of API calls to the Aruba AOS-CX API using Rust reqwests.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Dependencies
&lt;/h2&gt;

&lt;p&gt;First we need to add our dependencies to the project.&lt;br&gt;
To do this, update the cargo.toml file with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[dependencies]
reqwest = { version = "0.11", features = ["json", "cookies"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Login/logout
&lt;/h2&gt;

&lt;p&gt;Any interaction with the AOS-CX API requires a login, and, for good house-keeping, a logout.&lt;br&gt;
The API uses a username/password for authentication.&lt;/p&gt;

&lt;p&gt;With this snippet we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send a Login API call&lt;/li&gt;
&lt;li&gt;Check the request status code&lt;/li&gt;
&lt;li&gt;Print an error to screen if the login is unsuccessful.&lt;/li&gt;
&lt;li&gt;Send a logout call&lt;/li&gt;
&lt;li&gt;Check the logout call status code and print to screen if this is not 200.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ip_add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-ip-address"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://{}/rest/latest/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_add&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.cookie_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.danger_accept_invalid_certs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-username"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-password"&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login unsuccessful, code is {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}logout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Logout unsuccessful, code is {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Logout successful"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  3. GET some data
&lt;/h2&gt;

&lt;p&gt;Once we have the login/logout framework, we can insert API calls to GET, POST, PUT, and DELETE data.&lt;br&gt;
Here's an example that sends a GET call to query the firmware on a device.&lt;br&gt;
The response is JSON, which we can deserialize, using a predefined struct to handle the response key, value pairs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we define the struct, this is for the key 'current_version', with the value given a type of String.&lt;/li&gt;
&lt;li&gt;Then we use the reqwests client to define the URL for the GET and how to handle the returned JSON.&lt;/li&gt;
&lt;li&gt;Note that the name of the struct is referenced against &lt;code&gt;json&lt;/code&gt; in the response.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;current_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}firmware"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="py"&gt;.current_version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Dynamically parse JSON
&lt;/h2&gt;

&lt;p&gt;The example given in step 3 provides a great way to handle returned JSON, but we need to know what key,value pairs are returned from an API in order to be able to compose the struct.&lt;br&gt;
When investigating an API, it is often a case of trial and error to find the data that we need.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;So how can we send a GET call to receive data, without knowing what will be returned?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For this we can dynamically feed the JSON response into a hash-map:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we need to bring &lt;code&gt;std::collections::HashMap;&lt;/code&gt; into scope.&lt;/li&gt;
&lt;li&gt;We then send the same GET call as the second example.&lt;/li&gt;
&lt;li&gt;But for the JSON response we reference a dynamic hash-map, rather than a struct.&lt;/li&gt;
&lt;li&gt;The downside is that we cannot call specific keys to provide data directly, the key,value pairs are now merely strings, but we can print out the whole response.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
         &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}firmware"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
         &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  5. Putting it all together using a struct
&lt;/h2&gt;

&lt;p&gt;Here's the full code using a struct, I've added in an extra key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;current_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;booted_image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ip_add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-ip-address"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://{}/rest/latest/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_add&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.cookie_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.danger_accept_invalid_certs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-username"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-password"&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login unsuccessful, code is {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}firmware"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Current firmware image: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Boot firmware image: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="py"&gt;.current_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="py"&gt;.booted_image&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}logout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Logout unsuccessful, code is {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Logout successful"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  test1 git:(master) ✗ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/test1`
Current firmware image: FL.10.09.1020
Boot firmware image: primary
Logout successful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Putting it all together using a dynamic hash-map
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ip_add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-ip-address"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://{}/rest/latest/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_add&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.cookie_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.danger_accept_invalid_certs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-username"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-password"&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
        &lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Login unsuccessful, code is {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
         &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}firmware"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
         &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
         &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}logout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Logout unsuccessful, code is {:#?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Logout successful"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  test1 git:(master) ✗ cargo run
   Compiling test1 v0.1.0 (/Users/joeneville/code/test1)
    Finished dev [unoptimized + debuginfo] target(s) in 1.56s
     Running `target/debug/test1`
{
    "current_version": "FL.10.09.1020",
    "primary_version": "FL.10.09.1020",
    "secondary_version": "FL.10.09.1010",
    "default_image": "primary",
    "booted_image": "primary",
}
Logout successful
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Build an IPv6 Network with Docker Compose</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Fri, 10 Sep 2021 19:04:53 +0000</pubDate>
      <link>https://dev.to/joeneville_/build-an-ipv6-network-with-docker-compose-434i</link>
      <guid>https://dev.to/joeneville_/build-an-ipv6-network-with-docker-compose-434i</guid>
      <description>&lt;p&gt;✍️ In a &lt;a href="https://dev.to/joeneville_/build-a-docker-ipv6-network-dfj"&gt;previous post&lt;/a&gt;, I explained how to enable IPv6 for docker and build a network using the command-line&lt;br&gt;
💪 Here I detail how to do the same with docker compose.&lt;br&gt;
👉 Firstly, I build the network via the command-line then docker compose to attach a container to this network.&lt;br&gt;
🔥 Then, I build the network and container service, all in docker compose.&lt;br&gt;
💻 This demo was uses Ubuntu 21.04 server.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install docker compose
&lt;/h2&gt;

&lt;p&gt;The docker compose installation notes are &lt;a href="https://docs.docker.com/compose/install/"&gt;here&lt;/a&gt;. They worked for me without issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 1: Use an existing network with docker compose
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Manually create a network using the command-line. This is a user-defined bridge network. See my &lt;a href="https://dev.to/joeneville_/build-a-docker-ipv6-network-dfj"&gt;previous post&lt;/a&gt; for details about this type of network.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network create --ipv6 \
--subnet="2001:db8:1::/64" \
--gateway="2001:db8:1::1" \
mynetv6-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a docker-compose.yaml file that utilises the existing &lt;code&gt;mynetv6-1&lt;/code&gt; network.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.9"
services:
alp1:
image: alpine:latest
command: ping6 -c 4 2001:db8:1::1
networks:
  - mynetv6-1
networks:
mynetv6-1:
external: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;This file uses version 3.9.&lt;/li&gt;
&lt;li&gt;A service &lt;code&gt;alp1&lt;/code&gt; is created that uses the &lt;code&gt;alpine:latest&lt;/code&gt; image. This will ping the network gateway to demonstrate successful network &amp;amp; container build.&lt;/li&gt;
&lt;li&gt;There is a &lt;code&gt;networks&lt;/code&gt; subcommand under services to assign the service to the &lt;code&gt;mynetv6-1&lt;/code&gt; network.&lt;/li&gt;
&lt;li&gt;The dedicated &lt;code&gt;networks&lt;/code&gt; section references the existing &lt;code&gt;mynetv6-1&lt;/code&gt;, and indicates that this network is already live with the &lt;code&gt;external:true&lt;/code&gt; attribute. Without this, docker compose will attempt to create a new network for the &lt;code&gt;alp1&lt;/code&gt; service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;joe@ub5:~/docker-test$ docker-compose up
Creating docker-test_alp1_1 ... done
Attaching to docker-test_alp1_1
alp1_1  | PING 2001:db8:1::1 (2001:db8:1::1): 56 data bytes
alp1_1  | 64 bytes from 2001:db8:1::1: seq=0 ttl=64 time=0.087 ms
alp1_1  | 64 bytes from 2001:db8:1::1: seq=1 ttl=64 time=0.141 ms
alp1_1  | 64 bytes from 2001:db8:1::1: seq=2 ttl=64 time=0.051 ms
alp1_1  | 64 bytes from 2001:db8:1::1: seq=3 ttl=64 time=0.140 ms
alp1_1  |
alp1_1  | --- 2001:db8:1::1 ping statistics ---
alp1_1  | 4 packets transmitted, 4 packets received, 0% packet loss
alp1_1  | round-trip min/avg/max = 0.051/0.104/0.141 ms
docker-test_alp1_1 exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 2: Build a network and service with docker compose
&lt;/h2&gt;

&lt;p&gt;This example simply requires a docker-compose.yml file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "2.4"
services:
  alp2:
    image: alpine:latest
    command: ping6 -c 4 2001:db8:a::1
    networks:
      - net2
networks:
  net2:
    name: net2
    enable_ipv6: true
    ipam:
      config:
        - subnet: 2001:db8:a::/64
          gateway: 2001:db8:a::1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Note the version number, &lt;code&gt;enable_ipv6&lt;/code&gt; is not supported in version 3.&lt;/li&gt;
&lt;li&gt;This creates a service, with the latest alpine image, attached to the &lt;code&gt;net2&lt;/code&gt; network, and pings the gateway.&lt;/li&gt;
&lt;li&gt;The dedicated network section creates &lt;code&gt;net2&lt;/code&gt;, enables IPv6, and addresses the subnet and gateway.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;joe@ub5:~/docker-test$ docker-compose up --remove-orphans
Creating network "net2" with the default driver
Recreating docker-test_alp2_1 ... done
Attaching to docker-test_alp2_1
alp2_1  | PING 2001:db8:a::1 (2001:db8:a::1): 56 data bytes
alp2_1  | 64 bytes from 2001:db8:a::1: seq=0 ttl=64 time=2054.280 ms
alp2_1  | 64 bytes from 2001:db8:a::1: seq=1 ttl=64 time=1053.982 ms
alp2_1  | 64 bytes from 2001:db8:a::1: seq=2 ttl=64 time=53.688 ms
alp2_1  | 64 bytes from 2001:db8:a::1: seq=3 ttl=64 time=0.052 ms
alp2_1  |
alp2_1  | --- 2001:db8:a::1 ping statistics ---
alp2_1  | 4 packets transmitted, 4 packets received, 0% packet loss
alp2_1  | round-trip min/avg/max = 0.052/790.500/2054.280 ms
docker-test_alp2_1 exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final note: Want to keep the container running?
&lt;/h2&gt;

&lt;p&gt;Seeing as this is focused on docker networking, you might want to keep the container up and running, to ping about the network and such like.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just add &lt;code&gt;command: tail -F anything&lt;/code&gt; to your service.&lt;/li&gt;
&lt;li&gt;See &lt;a href="https://stackoverflow.com/questions/38546755/docker-compose-keep-container-running"&gt;here&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🐦 &lt;a class="mentioned-user" href="https://dev.to/joeneville_"&gt;@joeneville_&lt;/a&gt;
 &lt;/p&gt;

</description>
      <category>docker</category>
      <category>linux</category>
    </item>
    <item>
      <title>Configure Ubuntu WiFi Adapter with Netplan</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Tue, 07 Sep 2021 10:08:45 +0000</pubDate>
      <link>https://dev.to/joeneville_/configure-ubuntu-wifi-with-netplan-4je0</link>
      <guid>https://dev.to/joeneville_/configure-ubuntu-wifi-with-netplan-4je0</guid>
      <description>&lt;p&gt;👉 Here's a short explanation of how to configure an Ubuntu machine to join a wireless network, with Netplan.&lt;br&gt;
👉 This is for a wireless network with WPA2 Personal authentication (you need a password).&lt;br&gt;
👉 My test machine is running Ubuntu desktop 21.04.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Gather Required Details
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get the wireless network details.&lt;br&gt;
This is WPA2 Personal, you're going to need the usual details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSID&lt;/li&gt;
&lt;li&gt;Wireless network password&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get your Ubuntu machine's wireless adapter name.&lt;br&gt;
You can use &lt;code&gt;ip link&lt;/code&gt; or &lt;code&gt;ip add&lt;/code&gt; for this.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;joe@ub1:~$ ip add
...
3: wlx18d6c7116805: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc mq state UP group default qlen 1000
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this example, my adapter is &lt;code&gt;wlx18d6c7116805&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Compose the Netplan file
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a netplan yaml file in the /etc/netplan directory.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;In this example my file is called &lt;code&gt;mynet1.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure the wireless adapter details under &lt;code&gt;wifis&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The SSID for your wireless network (the name of the network) is configured under &lt;code&gt;access-points&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Record the wireless network password under the SSID.&lt;/li&gt;
&lt;li&gt;Make sure to configure any other networking adapters that you require as well, see my previous blog for examples of wired networks &lt;a href="https://dev.to/joeneville_/configure-ubuntu-networking-with-netplan-1cbc"&gt;here.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Example&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network:
  ethernets:
    eno1:
      addresses:
        - 10.150.15.25/24
  wifis:
    wlx18d6c7116805:
      dhcp4: yes
      dhcp6: yes
      access-points:
        "IDontLikeSand15":
          password: "Supersecure123"
  version: 2
  renderer: NetworkManager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Example notes&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The wired ethernet adapter &lt;code&gt;eno1&lt;/code&gt; is configured with a static IPv4 address.&lt;/li&gt;
&lt;li&gt;The wireless adapter &lt;code&gt;wlx18d6c7116805&lt;/code&gt; is configured for DHCPv4 and DHCPv6 address allocation.&lt;/li&gt;
&lt;li&gt;The SSID is &lt;code&gt;IDontLikeSand15&lt;/code&gt; with a password of &lt;code&gt;Supersecure123&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Apply the Netplan file
&lt;/h2&gt;

&lt;p&gt;Run the command &lt;code&gt;netplan apply &amp;lt;your-netplan-file-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Verify
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use the command &lt;code&gt;iwconfig&lt;/code&gt; to check the wireless adapter state.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ip add&lt;/code&gt; to view all your network adapter.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ iwconfig
lo        no wireless extensions.

eno1      no wireless extensions.

wlx18d6c7116805  IEEE 802.11bgn  ESSID:"IDontLikeSand15"  Nickname:"&amp;lt;WIFI@REALTEK&amp;gt;"
          Mode:Managed  Frequency:2.412 GHz  Access Point: 24:F2:7F:D1:89:81
          Bit Rate:72.2 Mb/s   Sensitivity:0/0
          Retry:off   RTS thr:off   Fragment thr:off
          Power Management:off
          Link Quality=100/100  Signal level=100/100  Noise level=0/100
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:0  Invalid misc:0   Missed beacon:0

$ ip add
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eno1: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 50:65:f3:2f:c9:a1 brd ff:ff:ff:ff:ff:ff
    altname enp0s25
    inet 10.150.15.25/24 brd 10.150.15.255 scope global noprefixroute eno1
       valid_lft forever preferred_lft forever
    inet6 2001:db8:15:0:5265:f3ff:fe2f:c9a1/64 scope global dynamic mngtmpaddr
       valid_lft 2591913sec preferred_lft 604713sec
    inet6 fe80::5265:f3ff:fe2f:c9a1/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: wlx18d6c7116805: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 18:d6:c7:11:68:05 brd ff:ff:ff:ff:ff:ff
    inet 192.168.15.74/24 brd 192.168.15.255 scope link noprefixroute wlx18d6c7116805
       valid_lft forever preferred_lft forever
    inet6 2001:db8:15:0:a402:f49a:d7be:5049/64 scope global temporary dynamic
       valid_lft 599939sec preferred_lft 81131sec
    inet6 2001:db8:15:0:1ad6:c7ff:fe11:6805/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 2591914sec preferred_lft 604714sec
    inet6 fe80::1ad6:c7ff:fe11:6805/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>linux</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Build a Docker IPv6 Network</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Thu, 26 Aug 2021 19:31:22 +0000</pubDate>
      <link>https://dev.to/joeneville_/build-a-docker-ipv6-network-dfj</link>
      <guid>https://dev.to/joeneville_/build-a-docker-ipv6-network-dfj</guid>
      <description>&lt;p&gt;6️⃣ Docker supports IPv6 addressing and IPv6 network builds.&lt;br&gt;
🔇 But IPv6 is not enabled by default.&lt;br&gt;
🔊 Here's how to turn on IPv6.&lt;br&gt;
🧱 Plus how to build three different v6 networks; the default docker0 bridge network, a user-defined bridge network, and an IPvlan network with access to the public Internet.&lt;br&gt;
🎬 TL;DR? Video link at the foot of the page 👇&lt;/p&gt;
&lt;h2&gt;
  
  
  How to enable Docker IPv6 support
&lt;/h2&gt;

&lt;p&gt;The official docs are &lt;a href="https://docs.docker.com/config/daemon/ipv6/" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but I think they could have done a better job with this. Here's my version.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a JSON file &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; and write to it a key of &lt;code&gt;ipv6&lt;/code&gt; with a value of &lt;code&gt;true&lt;/code&gt; and a key of &lt;code&gt;fixed-cidr-v6&lt;/code&gt; with a value of your chosen IPv6 prefix. This prefix will be assigned to the default docker bridge network, &lt;code&gt;docker 0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"ipv6"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"fixed-cidr-v6"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2001:db8:abc1::/64"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Save the file.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Restart docker: &lt;code&gt;systemctl restart docker&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Execute the command &lt;code&gt;docker network ls&lt;/code&gt;. Unless you have previously created your own custom networks, you will see the following:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
9ac6696dded9   bridge    bridge    local
bcc12f998444   host      host      local
32126ee8d073   none      null      local
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Inspect the default docker 'bridge' network with &lt;code&gt;docker network inspect bridge&lt;/code&gt;. You should see your chosen IPv6 network listed, as well as a gateway address.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;joe@ub6:~$ docker network inspect bridge
[
{
    "Name": "bridge",
    "Id": "9ac6696dded9bec310d3451552df8b399503b09b6019b879e08914f6dd056a9d",
    "Created": "2021-08-25T08:10:48.713298521Z",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": true,
    "IPAM": {
        "Driver": "default",
        "Options": null,
        "Config": [
            {
                "Subnet": "172.17.0.0/16",
                "Gateway": "172.17.0.1"
            },
            {
                "Subnet": "2001:db8:abc1::/64",
                "Gateway": "2001:db8:abc1::1"
            }
        ]
    },
    "Internal": false,
    "Attachable": false,
    "Ingress": false,
    "ConfigFrom": {
        "Network": ""
    },
    "ConfigOnly": false,
    "Containers": {},
    "Options": {
        "com.docker.network.bridge.default_bridge": "true",
        "com.docker.network.bridge.enable_icc": "true",
        "com.docker.network.bridge.enable_ip_masquerade": "true",
        "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
        "com.docker.network.bridge.name": "docker0",
        "com.docker.network.driver.mtu": "1500"
    },
    "Labels": {}
}
]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;
. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Execute the &lt;code&gt;ip add&lt;/code&gt; command and the &lt;code&gt;docker0&lt;/code&gt; interface now shows an IPv6 address:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4: docker0: &amp;lt;NO-CARRIER,BROADCAST,MULTICAST,UP&amp;gt; mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:dc:01:46:7a brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
   valid_lft forever preferred_lft forever
inet6 2001:db8:abc1::1/64 scope global
   valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create an IPv6 container
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a container as usual. By default this is assigned to the docker0 bridge network and has an IPv4 &amp;amp; IPv6 address:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker run -di --name alpine6-1 alpine
0f1e634aacf45bfccca1494d6804bbaeac21b870152ceebdaeea8ad72ae27b3d
$ docker exec alpine6-1 ip a
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
   valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
   valid_lft forever preferred_lft forever
22: eth0@if23: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN&amp;gt; mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
   valid_lft forever preferred_lft forever
inet6 2001:db8:abc1::242:ac11:2/64 scope global flags 02
   valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:2/64 scope link
   valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;
. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ping the local gateway:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;joe@ub6:~$ docker exec alpine6-1 ping6 2001:db8:abc1::1
PING 2001:db8:abc1::1 (2001:db8:abc1::1): 56 data bytes
64 bytes from 2001:db8:abc1::1: seq=0 ttl=64 time=0.133 ms
64 bytes from 2001:db8:abc1::1: seq=1 ttl=64 time=0.143 ms
64 bytes from 2001:db8:abc1::1: seq=2 ttl=64 time=0.123 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;The docker0 network is the default bridge network. By default, containers assigned to it will now have IPv4 and IPv6 connectivity to the local gateway and each other.&lt;/li&gt;
&lt;li&gt;However, due to limitations with the docker0 network, the advice from docker is that this network should not be used in Production environments, user-defined bridge networks should be used instead. In the next section I will build one such custom network.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User-defined bridge network build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The user-defined bridge network is similar to the docker0 network; it resides on the docker host, and the host acts as the network's gateway.&lt;/li&gt;
&lt;li&gt;The containers assigned to a user-defined bridge network have access to other containers on the same network, to the gateway and to external networks.&lt;/li&gt;
&lt;li&gt;However, because the docker host performs the role of a gateway, external networks will need to have the necessary routing information to be able to communicate with the custom network. See the diagram below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rwjb6wdr6cn697ydo3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rwjb6wdr6cn697ydo3o.png" alt="Screenshot 2021-09-01 at 22.37.24" width="581" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In this example, the external server, by default, will not have a route to the user-defined bridge network's subnet, named my-net1 (2001:db8:1::1/64). &lt;/li&gt;
&lt;li&gt;The docker host acts as the gateway between the external network (2001:db8:eeee::/64) and my-net1.&lt;/li&gt;
&lt;li&gt;The containers attached to my-net1 will be able to send traffic to external networks, using the docker host as their default gateway, but return traffic will fail, the external server will lack the required information to return traffic to the containers.&lt;/li&gt;
&lt;li&gt;Therefore, this situation can be improved by adding a static route to the external server, pointing to 2001:db8:1::/64 via the docker host (2001:db8:eeee::/64).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build Step-by-step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Enable IPv6 for docker, see above for details.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new IPv6 bridge network with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network create --subnet="&amp;lt;your-v6-prefix&amp;gt;" \
--gateway="your-gateway-address" \
--ipv6 \
&amp;lt;name-of-bridge-network&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attach a container to this network using the &lt;code&gt;--network&lt;/code&gt; flag for &lt;code&gt;docker run&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify with &lt;code&gt;docker network inspect &amp;lt;your-network&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For external access, ensure destination networks have a route to &lt;code&gt;&amp;lt;your-v6-prefix&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step-by-step example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# Create the network
$ docker network create --subnet="2001:db8:1::/64" \
    --gateway="2001:db8:1::1" \
    --ipv6 \
    my-net1
# Create and attach a container
$ docker run -di --name alpine-1 \
    --network my-net1 \
    alpine:latest
# Verify
$ docker network inspect my-net1
[
    {
        "Name": "my-net1",
        "Id": "c71cbef39206ccaf037db82a12993e59851c1ca9ad18f45975cff8a96f85240a",
        "Created": "2021-09-02T08:13:38.448154937Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": true,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.20.0.0/16",
                    "Gateway": "172.20.0.1"
                },
                {
                    "Subnet": "2001:db8:1::/64",
                    "Gateway": "2001:db8:1::1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "736667de9c88a66fa14aa664eaad827bac7e68776288997361b5cda157949b65": {
                "Name": "alpine-1",
                "EndpointID": "88e56b4c8acbc4342ff0f3c0624a8e801b7fc3f4002339d14450645117ddd5b5",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/16",
                "IPv6Address": "2001:db8:1::2/64"
            }
        },
        "Options": {},
        "Labels": {}
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  A Note on external communication for IPv6 containers
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;In this example I've used the IPv6 prefix assigned to documentation for illustrative purposes.&lt;/li&gt;
&lt;li&gt;As previously mentioned, for containers on this network to communicate with external destinations, the external nodes will require the routing information to the internal v6 network.&lt;/li&gt;
&lt;li&gt;If you wish to provide your containers with access to the public internet, their attached network will need to be a globally routable prefix.&lt;/li&gt;
&lt;li&gt;In my home lab, my ISP currently only provides one globally routable prefix, assigned to my LAN. (I have a /56 assigned to my home but the ISP router is currently locked to only act as the gateway for a single /64 on the LAN).&lt;/li&gt;
&lt;li&gt;In this case, I opted for an IPvlan Layer 2 docker network, this uses the docker host's IPv6 network. In my case, this network is the ISP assigned globally routable network.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  IPvlan network build
&lt;/h2&gt;

&lt;p&gt;The official docs for IPvlan are &lt;a href="https://docs.docker.com/network/ipvlan/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'm using the default L2 mode.&lt;/li&gt;
&lt;li&gt;In this mode, the docker host parent interface will operate at Layer 2 only and switch traffic from containers to the external gateway, in my case this is my ISP router, which will then forward on the traffic to the public internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the diagram below; the parent host interface, the containers, and the external gateway sit in the same IPv6 subnet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F766x2jknpq00pc9geffr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F766x2jknpq00pc9geffr.png" alt="Screenshot 2021-08-26 at 17.28.31" width="339" height="408"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Build Step-by-step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create the IPvlan network with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker network create -d ipvlan \
--subnet=&amp;lt;your-network-address&amp;gt;::/64 \
--gateway=&amp;lt;your-gateway-address&amp;gt;  \
--ipv6 -o parent=&amp;lt;your-host-parent-interface&amp;gt; \
&amp;lt;your-network-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The parent interface sits in the same subnet as the gateway. Check 'ip addr' to confirm the interface name.&lt;br&gt;
The created network is dual-stack, with both IPv4 and IPv6 addressing. Unless you specifically configure the IPv4 addressing, it is a /16 taken from the 172/8 range.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new container and connect it to the IPvlan network.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -di --name alpine6-2 \
  --network &amp;lt;your-network-name&amp;gt; alpine
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This creates a container and attaches it to the IPvlan network.&lt;br&gt;
The container is dual-stack, with an IPv4 address and IPv6 address. &lt;br&gt;
Verify with &lt;code&gt;ip addr&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To test, ping an IPv6 site, such as &lt;code&gt;youtube.com&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker exec alpine6-2 ping youtube.com
PING youtube.com (2a00:1450:4009:815::200e): 56 data bytes
64 bytes from 2a00:1450:4009:815::200e: seq=0 ttl=117 time=11.663 ms
64 bytes from 2a00:1450:4009:815::200e: seq=1 ttl=117 time=16.935 ms
64 bytes from 2a00:1450:4009:815::200e: seq=2 ttl=117 time=11.311 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  TL;DR? 👇
&lt;/h1&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ghqBzad5efc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>docker</category>
      <category>networking</category>
      <category>ipv6</category>
    </item>
    <item>
      <title>Still got love for Python format()</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Fri, 12 Feb 2021 12:05:41 +0000</pubDate>
      <link>https://dev.to/joeneville_/still-got-love-for-string-format-4ck3</link>
      <guid>https://dev.to/joeneville_/still-got-love-for-string-format-4ck3</guid>
      <description>&lt;p&gt;✍️ In my previous post about Python f-strings, I was a little too hard on the older string formatting approach, format().&lt;br&gt;
👀 I was focused solely the string formatting in terms of string substitution, for which I find f-strings the more intuitive and superior approach.&lt;br&gt;
🧰 However, as some devs rightly pointed out, format has some other, very useful, features, and should not be consigned to the mental dustbin. But rather dusted off and placed alongside f-strings in your Python toolkit when it comes to strings.&lt;/p&gt;
&lt;h2&gt;
  
  
  format() uses
&lt;/h2&gt;
&lt;h3&gt;
  
  
  👉 Formatting strings with variable substitution and concatenation
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"know"&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"thanks!"&lt;/span&gt;
&lt;span class="n"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Yes, we already {} this, {} {}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;we&lt;/span&gt; &lt;span class="n"&gt;already&lt;/span&gt; &lt;span class="n"&gt;know&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thanks&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So far, so f-strings.&lt;/p&gt;
&lt;h3&gt;
  
  
  👉 String formatting with padding
&lt;/h3&gt;

&lt;p&gt;If you are struggling to structure your string lay-out and presenting them in a way that is pleasing to the eye, format can help. Rather than having to count out 26 whitespaces each time (or was it 25) to get them to line up, you can use padding.&lt;br&gt;
Rather than just use empty curly braces, padding allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define a field size to be inserted into the string&lt;/li&gt;
&lt;li&gt;position your characters&lt;/li&gt;
&lt;li&gt;define the fill characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the curly braces, to the right of a colon, add the minimum number of character fields: &lt;code&gt;{:20}&lt;/code&gt; this will be 20 fields minimum.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Give me some {:20} please!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Give&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;                &lt;span class="n"&gt;please&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use "&amp;lt;" to left-align, "&amp;gt;" to right align and "=" to centre. The default is to left-align, as above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Give me some {:&amp;gt;20} please!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Give&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt;                &lt;span class="n"&gt;space&lt;/span&gt; &lt;span class="n"&gt;please&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Give me some {:^20} please!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Give&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt;        &lt;span class="n"&gt;space&lt;/span&gt;         &lt;span class="n"&gt;please&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add in a character to fill the designed number of spaces, the default is just whitespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Give me some {:*^20} please!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Give&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="o"&gt;*******&lt;/span&gt;&lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="o"&gt;********&lt;/span&gt; &lt;span class="n"&gt;please&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if you want to use your own fill characters, you need to set the alignment as well, not rely on the default. Thus for left-align:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This will throw an error
&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Give me some {:*20} please!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;stdin&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Invalid&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="n"&gt;specifier&lt;/span&gt;
&lt;span class="c1"&gt;# Define the alignment
&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Give me some {:*&amp;lt;20} please!"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"space"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;Give&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="o"&gt;***************&lt;/span&gt; &lt;span class="n"&gt;please&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, format uses a "mini-language" that has numerous other options. &lt;a href="https://docs.python.org/3.4/library/string.html#format-specification-mini-language"&gt;Check the Python docs for more info.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's going to be some uses for padding, but if you are trying to align your text into a table, there are Python libraries for that, which are probably a better option, like &lt;a href="https://pypi.org/project/tabulate/"&gt;tabulate.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  👉 Accessing Dictionary Values
&lt;/h3&gt;

&lt;p&gt;If you're working with Python dictionaries, format has another useful feature for you. You can access the values of a dictionary directly from format. No need to unpack to variables then reference them, you can call the dictionary inside the format argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;paint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"colour"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sky Blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s"&gt;"For sale, {volume} litres of {colour} paint"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;paint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s"&gt;'For sale, 5 litres of Sky Blue paint'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  👉 Check this docs for more
&lt;/h3&gt;

&lt;p&gt;format() has even more features that those listed here, &lt;a href="https://docs.python.org/3.4/library/string.html#format-examples"&gt;check the Python docs for more examples.&lt;/a&gt;&lt;br&gt;
Also, I've used &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-string-formatters-in-python-3"&gt;this blog from Digital Ocean&lt;/a&gt; a few times. It presents information about format() in a style that I found easier to understand than diving straight in the official docs.&lt;/p&gt;

&lt;p&gt;In conclusion, f-strings and format(); group hug. 🤗&lt;/p&gt;

</description>
      <category>python</category>
      <category>coding</category>
    </item>
    <item>
      <title>Use Python f-strings and never look back!</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Mon, 21 Dec 2020 18:28:55 +0000</pubDate>
      <link>https://dev.to/joeneville_/use-python-f-strings-and-never-look-back-4pbg</link>
      <guid>https://dev.to/joeneville_/use-python-f-strings-and-never-look-back-4pbg</guid>
      <description>&lt;p&gt;❓Are you using f-strings to format your strings in Python? Yes? Great. &lt;br&gt;
🚀 If not, consider dropping your tired old format approach and get with the new(ish) stuff: f-strings.&lt;br&gt;
📚 There's plenty of blogs out there that tell you all about f-strings. They tell you at length about the old approaches, % formatting and .format(), so I'm not going to go over all that, I'm just here to encourage you to take a look at f-strings. &lt;br&gt;
🤔 &lt;strong&gt;Why?&lt;/strong&gt; I'm using f-strings all the time now, and .format() seems clunky and overly verbose in comparison.&lt;/p&gt;
&lt;h2&gt;
  
  
  Here's the basics:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;f-strings is the name given to 'formatted string literals'&lt;/li&gt;
&lt;li&gt;Python 3.6+&lt;/li&gt;
&lt;li&gt;Similar in approach to .format() but less verbose, which helps keep your line length down and improve code readability.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  A few examples
&lt;/h2&gt;

&lt;p&gt;Here's a simple combination of string variable declaration and print using .format():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"clocks"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"thirteen"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sentence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"The {} were striking {}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;clocks&lt;/span&gt; &lt;span class="n"&gt;were&lt;/span&gt; &lt;span class="n"&gt;striking&lt;/span&gt; &lt;span class="n"&gt;thirteen&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the same code, but using f-string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"clocks"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"thirteen"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sentence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"The &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; were striking &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;clocks&lt;/span&gt; &lt;span class="n"&gt;were&lt;/span&gt; &lt;span class="n"&gt;striking&lt;/span&gt; &lt;span class="n"&gt;thirteen&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the two approaches it doesn't seem like much of a difference. But I think the improvement are enough to make me switch to f-strings and never look back.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;F-strings save you eight characters by using a single 'f' to declare that the code uses f-strings, rather than the '.format()'.&lt;/li&gt;
&lt;li&gt;Writing code is more intuitive, I feel, adding the string variables within the string itself, rather than saving this for the end of the line.&lt;/li&gt;
&lt;li&gt;Reading code is easier because the declarations are right there in the string, again, rather than at the end of the line.&lt;/li&gt;
&lt;li&gt;N.B. .format does allow variables in the braces, but that has the additional requirement of having to declare those in the final brackets, like so:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sentence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"The {device} were striking {time}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this approach is even more verbose.&lt;/p&gt;

&lt;p&gt;In isolation, all this points may seem minor, but as string formatting is such a necessary part of so much Python code, these small improvements can make a big difference to the overall experience of reading and writing Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some extra details
&lt;/h2&gt;

&lt;p&gt;Quotation marks can be used within an f-string, as long as different types are used&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"quoted string"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
&lt;span class="s"&gt;'quoted string'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Methods can be called directly&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sentence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"The &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; were striking &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;CLOCKS&lt;/span&gt; &lt;span class="n"&gt;were&lt;/span&gt; &lt;span class="n"&gt;striking&lt;/span&gt; &lt;span class="n"&gt;thirteen&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more details, check out PEP 498, the proposal that was developed into f-strings &lt;a href="https://www.python.org/dev/peps/pep-0498/"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give f-strings a go, I hope you find them as useful as I have.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

&lt;p&gt;🐦 &lt;a class="mentioned-user" href="https://dev.to/joeneville_"&gt;@joeneville_&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>python</category>
      <category>coding</category>
    </item>
    <item>
      <title>Dnsmasq - Lightweight Name Resolution For Your Home Lab</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Thu, 17 Dec 2020 08:33:55 +0000</pubDate>
      <link>https://dev.to/joeneville_/dnsmasq-lightweight-name-resolution-for-your-lab-2gim</link>
      <guid>https://dev.to/joeneville_/dnsmasq-lightweight-name-resolution-for-your-lab-2gim</guid>
      <description>&lt;p&gt;If you need name resolution (DNS) for a small network or home lab, Dnsmasq is worth investigating.&lt;/p&gt;

&lt;p&gt;💻 Runs on Linux, macOS.&lt;br&gt;
📦 Simple package install with apt-get (on ubuntu linux).&lt;br&gt;
📛 Provides DNS and DHCP services - I'm just running DNS here.&lt;/p&gt;

&lt;p&gt;The only downside I've encountered so far is that, while the man page is detailed, some user produced 'how to' guides use various different ways to configure the setup for Dnsmasq.&lt;/p&gt;

&lt;p&gt;I've documented the steps that worked for me.&lt;/p&gt;
&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I'm building a private Public Key Infrastructure in my home lab.&lt;/li&gt;
&lt;li&gt;For the lab I need name resolution.&lt;/li&gt;
&lt;li&gt;I'm learning and documenting as I go, all servers and their configurations should to be disposable. &lt;/li&gt;
&lt;li&gt;I'd like an approach that allows quick and easy updates to DNS records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've found that Dnsmasq is a good fit for my requirements.&lt;/p&gt;

&lt;p&gt;Here's my install notes for a simple two server install using Dnsmasq for DNS only. I cover IPv4 &amp;amp; A records only but Dnsmasq supports IPv6:&lt;/p&gt;
&lt;h2&gt;
  
  
  Dnsmasq Install Step-by-Step
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build two linux server VMs, I'm using Ubuntu 20.04.1.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update and upgrade&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get -y install
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Use Netplan (or whichever approach you prefer) to statically configure IP addresses, default gateway, name servers and search domain. See &lt;a href="https://dev.to/joeneville_/configure-ubuntu-networking-with-netplan-1cbc"&gt;here&lt;/a&gt; if you need more information on Netplan.&lt;br&gt;
Note: On all server that are not running Dnsmasq, point their name server configuration to the Dnsmasq server IP address.&lt;br&gt;
Here's an example Netplan YAML file:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network:
  ethernets:
    eth0:
      dhcp4: False
      addresses:
        - 192.168.1.21/16
        - "2001:db8:2::21/64"
      gateway4: 192.168.1.254
      gateway6: "2001:db8:2::99"
      nameservers:
        search: [zola.home]
        addresses: [192.168.1.20]
  version: 2
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;On the name server, install Dnsmasq&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get -y install dnsmasq
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;The package will attempt to start Dnsmasq, this will fail because the port is in use already. This is fine and we will fix later.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dec 16 10:59:59 name-1 dnsmasq[1165]: dnsmasq: failed to create listening socket for port 53: Address already in use
Dec 16 10:59:59 name-1 dnsmasq[1165]: failed to create listening socket for port 53: Address already in use
Dec 16 10:59:59 name-1 dnsmasq[1165]: FAILED to start up
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Now turn off the systemd-resolved and delete the resolved config file.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo rm -v /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Configure Dnsmasq config file &lt;code&gt;/etc/dnsmasq.conf&lt;/code&gt;.&lt;br&gt;
Here's a sample config file:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Never forward plain names (without a domain)
domain-needed
# Turn off DHCP on eth0
no-dhcp-interface=eth0
# Never forward addresses in the non-routable address space (RFC1918)
bogus-priv
# Add domain to host names
expand-hosts
# Domain to be added if expand-hosts is set
domain=zola.home
# Local domain to be served from /etc/hosts file
local=/zola.home/
# Don't read /etc/resolv.conf (I deleted it). Get the external name server from this file, see 'server' below
no-resolv
# External server, works with no-resolv
server=8.8.8.8
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Update the Dnsmasq server /etc/hosts file with the name and IP address of the hosts you wish to resolve.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1 localhost
192.168.1.20 name-1
192.168.1.21 server-2
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Start and Enable the Dnsmasq service.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl start dnsmasq
sudo systemctl enable dnsmasq
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;To test: ping from the Dnsmasq server to an external site, itself and a neighbouring server. Do the same on Server-2.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;joe@server-2:~$ ping name-1
PING name-1.zola.home (192.168.1.20) 56(84) bytes of data.
64 bytes from name-1.zola.home (192.168.1.20): icmp_seq=1 ttl=64 time=0.152 ms
64 bytes from name-1.zola.home (192.168.1.20): icmp_seq=2 ttl=64 time=0.221 ms
^C
--- name-1.zola.home ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1023ms
rtt min/avg/max/mdev = 0.152/0.186/0.221/0.034 ms
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/uV-OauHhKgA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>linux</category>
      <category>dns</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Configure Ubuntu Networking with Netplan</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Thu, 03 Sep 2020 21:15:36 +0000</pubDate>
      <link>https://dev.to/joeneville_/configure-ubuntu-networking-with-netplan-1cbc</link>
      <guid>https://dev.to/joeneville_/configure-ubuntu-networking-with-netplan-1cbc</guid>
      <description>&lt;h1&gt;
  
  
  Why configure a static IP address
&lt;/h1&gt;

&lt;p&gt;👉 By default, &lt;em&gt;Ubuntu server boots with DHCPv4 enabled&lt;/em&gt;. &lt;br&gt;
👉 While dynamic IP addressing is convenient, it is more aligned with clients, that joins and leave networks, rather than servers, which tend to remain fixed on a network. &lt;br&gt;
👉 If you're building anything beyond the most basic deployment, it is wise to plan out your server infrastructure. &lt;br&gt;
👉 That means using &lt;strong&gt;static IP addressing&lt;/strong&gt; for your servers, which requires a little knowledge about how to configure netplan.&lt;/p&gt;
&lt;h1&gt;
  
  
  Enter netplan
&lt;/h1&gt;

&lt;p&gt;Ubuntu uses netplan to control its networking configuration. It is a powerful tool, consolidating the regular networking configuration points, such as interface addressing and DNS servers, with more advanced features, such as tunneling, interface bonding and security.&lt;/p&gt;
&lt;h1&gt;
  
  
  Yet more YAML
&lt;/h1&gt;

&lt;p&gt;We configure netplan by writing a YAML file according to a fixed structure, then applying said file.&lt;/p&gt;

&lt;p&gt;Go to the &lt;a href="https://netplan.io/examples/"&gt;netplan examples site&lt;/a&gt; to get an idea of the required structure.&lt;/p&gt;
&lt;h1&gt;
  
  
  Netplan is annoying. Netplan examples? Also annoying!
&lt;/h1&gt;

&lt;p&gt;Netplan itself is pretty annoying. Not only are there &lt;em&gt;no default examples in the base file&lt;/em&gt;, the structure and syntax need to be exact. Cue some trial and error to get the yaml to work.&lt;br&gt;
In addition to this, the examples given on the examples page are seemingly a random mix of features. There's a definite bias towards IPv4 on that page as well, IPv6 configuration is not worthy of its own snippet and has to just co-star in some other feature's example.&lt;/p&gt;
&lt;h1&gt;
  
  
  How to configure netplan
&lt;/h1&gt;
&lt;h2&gt;
  
  
  A netplan workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;As new install of ubuntu server 18.04 &amp;amp; 20.04 will have a default netplan yaml file stored in &lt;code&gt;/etc/netplan&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;  joe@ubserv2:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /etc/netplan
  00-installer-config.yaml
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;I usually copy this as a starting point.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;00-installer-config.yaml netplan1.yaml
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure the new yaml file as required. See below for an example.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Netplan can verify the syntax of the new file before applying it with the &lt;code&gt;netplan generate &amp;lt;your-file-name&amp;gt;&lt;/code&gt; command.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once you are ready to go, activate your new networking configuration with &lt;code&gt;netplan apply &amp;lt;your-file-name&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you are using an SSH connection into the device using the previously configured IP address, your connection will be cut at this point.&lt;br&gt;&lt;br&gt;
To avoid being locked out from your server, use the &lt;br&gt; &lt;code&gt;netplan try &amp;lt;your-file-name&amp;gt;&lt;/code&gt; command. This will apply your selected netplan yaml file but will rollback to the previous configuration if you do not confirm the change within 120 seconds.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A netplan example
&lt;/h2&gt;

&lt;p&gt;Here's an example that will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure static IPv4 and IPv6 on an ethernet interface (eth1).&lt;/li&gt;
&lt;li&gt;Configure default gateways for v4 and v6.&lt;/li&gt;
&lt;li&gt;Configure v4 and v6 nameservers.&lt;/li&gt;
&lt;li&gt;Set a DNS search-list of "mynet.home".&lt;/li&gt;
&lt;/ol&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;network:
  ethernets:
    eth1:
      addresses:
        - 192.168.20.99/24
        - "2001:db8:20::99/64"
      gateway4: 192.168.20.1
      gateway6: "2001:db8:20::99"
      nameservers:
        search: [mynet.home]
        addresses: [8.8.8.8, "2001:4860:4860::8888"]
  version: 2
&lt;/code&gt;&lt;/pre&gt;

</description>
      <category>ubuntu</category>
      <category>linux</category>
      <category>networking</category>
      <category>netplan</category>
    </item>
    <item>
      <title>Automate Hyper-V VM Creation with Powershell</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Wed, 26 Aug 2020 08:53:14 +0000</pubDate>
      <link>https://dev.to/joeneville_/automate-hyper-v-vm-creation-with-powershell-1fgc</link>
      <guid>https://dev.to/joeneville_/automate-hyper-v-vm-creation-with-powershell-1fgc</guid>
      <description>&lt;p&gt;If you're using Hyper-V for your virtualization, the Hyper-V Manager GUI provides a serviceable, if a little dull, interface. &lt;br&gt;
However, the bland colour scheme is not the only problem with the graphical interface, its true weakness is just how limited and inefficient it is. Using the wizard, only basic functionality is available, forcing users to click through the presented screens, before going into the virtual machine's 'Settings' to select such rudimentary features as the number of CPUs and Networking configuration.&lt;/p&gt;

&lt;p&gt;If you have been paying attention to the messaging coming from Microsoft for years now, it should be no surprise that automating your operations with powershell scripting is a more efficient alternative to all that mouse clicking.&lt;/p&gt;

&lt;p&gt;If you dislike powershell, I can sympathize with you, I find it to be quite an ugly customer when compared to the elegance of well-written Python. But, this isn't a beauty contest, we are here to get the job done and leave work on time. Powershell for VM creation it is then!&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample powershell script for a new VM
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Specs 📝
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A new VM with 2 CPUs, 8GB RAM and 20GB storage. &lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Networking 🤝
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The VM should be attached via its 'port1' to my local vswitch, named "vswitch1".&lt;/li&gt;
&lt;li&gt;The port will be an access port in VLAN199.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  To Finish ⌛️
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Once built, start the VM.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This script is in two parts. First we declare the variables to be applied.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my_new_vm"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# name of VM, this just applies in Windows, it isn't applied to the OS guest itself.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path\to\vm\iso\file.iso"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$vmswitch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vswitch1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# name of your local vswitch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"port1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# port on the VM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$vlan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;199&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# VLAN that VM traffic will be send in&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Number of CPUs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ram&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;GB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# RAM of VM. Note this is not a string, not in quotation marks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$path_to_disk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path\to\disk"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Where you want the VM's virtual disk to reside&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$disk_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;GB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# VM storage, again, not a string&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# The following are the powershell commands&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Create a new VM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-VM&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Set the CPU and start-up RAM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-VM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ProcessorCount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cpu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-MemoryStartupBytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ram&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="c"&gt;# Create the new VHDX disk - the path and size.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-VHD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$path_to_disk$vm&lt;/span&gt;&lt;span class="nt"&gt;-disk1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vhdx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SizeBytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$disk_size&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Add the new disk to the VM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-VMHardDiskDrive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-VMName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$path_to_disk$vm&lt;/span&gt;&lt;span class="nt"&gt;-disk1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vhdx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Assign the OS ISO file to the VM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-VMDvdDrive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-VMName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Remove the default VM NIC named 'Network Adapter'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-VMNetworkAdapter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-VMName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="c"&gt;# Add a new NIC to the VM and set its name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-VMNetworkAdapter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-VMName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Configure the NIC as access and assign VLAN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-VMNetworkAdapterVlan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-VMName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-VMNetworkAdapterName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AccessVlanId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vlan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Connect the NIC to the vswitch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Connect-VMNetworkAdapter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-VMName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SwitchName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vmswitch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Fire it up 🔥&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-VM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vm&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the VM will boot and you can complete the install process.&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>windows</category>
      <category>virtualization</category>
    </item>
    <item>
      <title>How to run virtual environments with different versions of Python</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Fri, 14 Aug 2020 07:37:03 +0000</pubDate>
      <link>https://dev.to/joeneville_/how-to-run-virtual-environments-with-different-versions-of-python-12g</link>
      <guid>https://dev.to/joeneville_/how-to-run-virtual-environments-with-different-versions-of-python-12g</guid>
      <description>&lt;h1&gt;
  
  
  Always use a Virtual Environment
&lt;/h1&gt;

&lt;p&gt;🦉 It is a wise move to always run your Python projects in a virtual environment.&lt;br&gt;
🎈 Installing the many dependencies that the average Python program requires into your base OS can soon lead to a bloated system.&lt;br&gt;
🍱 Better to run a virtual environment per project and keep everything clean and compartmentalized.&lt;/p&gt;
&lt;h1&gt;
  
  
  Python 2 and Python 3?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;With that in mind, I had a requirement recently to run a program that required Python 2.7. I was running Python 3.8 on my machine.&lt;/li&gt;
&lt;li&gt;I realized that a virtual environment itself wasn't going to be enough. By default my virtual environments were all using Python 3.8.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how I approached the problem on Windows 10:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Python2.7 from python.org&lt;/p&gt;

&lt;p&gt;Make sure you take a note of where the .exe file is installed. On my Windows machine the path was:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Python27\python.exe
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Install the Python package 'virtualenv' using pip:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install virtualenv
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Create a new virtual environment using the Python 2.7 executable.&lt;/p&gt;

&lt;p&gt;Here the virtual environment is called venv1 and it uses Python2.7.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;virtualenv -p C:\Python27\python.exe venv1
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Activate the virtual environment.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.\venv1\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Now if I enter 'python' I can see that I'm using Python 2.7.18 rather than the default 3.8:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(venv1) PS C:\Users\joe&amp;gt; python
Python 2.7.18&amp;lt;snip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;To create a virtual environment with Python 3.8, I can just run virtualenv without the p flag:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;virtualenv venv2
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;✨👹✨&lt;br&gt;
&lt;a class="mentioned-user" href="https://dev.to/joeneville_"&gt;@joeneville_&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>python</category>
      <category>windows</category>
    </item>
    <item>
      <title>JupyterLab in a virtual environment</title>
      <dc:creator>Joe Neville</dc:creator>
      <pubDate>Thu, 09 Jul 2020 16:05:46 +0000</pubDate>
      <link>https://dev.to/joeneville_/jupyterlab-in-a-virtual-environment-gf5</link>
      <guid>https://dev.to/joeneville_/jupyterlab-in-a-virtual-environment-gf5</guid>
      <description>&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;🏋️‍♀️ When it comes to running JupyterLab &amp;amp; JupyterNotebooks, the install is pretty hefty, with quite a few dependencies.&lt;br&gt;
🦉 To avoid the travails of picking through a messy base install of Python on your dev machine, it is wise to always use a virtual environment.&lt;br&gt;
👍 Thus running JupyterLab in a virtual environment is a &lt;em&gt;good idea&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How?
&lt;/h2&gt;

&lt;p&gt;Here's how using macOS.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to a suitable directory in which want to work, or make a new one.&lt;/li&gt;
&lt;li&gt;Copy the Jupyter Notebook that you want to work on to this directory.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a virtual environment and give it a name, in the example below, I'm using 'jup1'.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv jup1
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Activate the the virtual environment.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;jup1/bin/activate
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Now that the venv is active we can install JupyterLab.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;jupyterlab
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Start JupyterLab.&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;jupyter lab
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Extra Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The shortcuts for Jupyter Notebooks really help to speed up working with them. It pays to invest some time in learning them. Or, if you can't be bothered with that, just remember that 'shift' + 'return' runs a cell.&lt;/li&gt;
&lt;li&gt;If you want to check your notebooks into version control, such as github, make sure you clear the outputs first. To do so right-click on the notebook, or Edit &amp;gt; Clear All Outputs. Then save and do your commits. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>jupyter</category>
    </item>
  </channel>
</rss>
