<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Remko Lodder</title><link>/</link><description>Recent content on Remko Lodder</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Copyright © 2003-2026, Remko Lodder, all rights reserved.</copyright><lastBuildDate>Sat, 13 Dec 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://www.evilcoder.org/index.xml" rel="self" type="application/rss+xml"/><item><title>Certified as NLP Practitioner</title><link>/posts/2025-12-13-certified-nlp-practitioner/</link><pubDate>Sat, 13 Dec 2025 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2025-12-13-certified-nlp-practitioner/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As you might, or might not be aware, in my day to day job, I am a manager / coach for
a group of highly technical experts. I am happy that I am able to be their manager
and being able to coach them on a daily basis. This means I strive to improve every day myself so that I can give my best version to them. And how can I ask of my colleagues to grow if I am not growing as well?&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>As you might, or might not be aware, in my day to day job, I am a manager / coach for
a group of highly technical experts. I am happy that I am able to be their manager
and being able to coach them on a daily basis. This means I strive to improve every day myself so that I can give my best version to them. And how can I ask of my colleagues to grow if I am not growing as well?</p>
<p>One can grow by doing courses, learning new things, or for example discover yourself
more and more.</p>
<p>One of those methods for me that worked out pretty well is the NLP Practitioner course.
NLP for me is a highly skilled course (not a training) that enables to you start becoming an excellent communicator and observer. I find this important, things I say
have weight, and things that I observe and hear, have a meaning as well. Being able
to interpret them at the right levels, means I can relate easier to what is going on
and align where possible.</p>
<h2 id="what-is-nlp">What is NLP?</h2>
<p>NLP stands for Neuro-Linguistic-Programming:</p>
<ul>
<li>
<p>The Neuro represents the neurology, the brain, how do we think, where do we think, what makes me me? How is information interpreted?</p>
</li>
<li>
<p>Linguistic is the language, and then specifically the art of the language. How are my
words interpreted? How do I avoid unclear sentences ? What if I get a push back, did I
push too hard myself?</p>
</li>
<li>
<p>Programming stands obviously for &lsquo;programming&rsquo; yourself. What can I do to alter the way I communicate? If I know that the other has a preference to visualize things, would it then help if I talk about feelings? No I need to level up to the other so that we speak the same language.</p>
</li>
</ul>
<p>Sounds simple eh? Well, before you can become successful in this, I needed a long running course.</p>
<h2 id="why-find-ways-to-discover-yourself">Why find ways to discover yourself?</h2>
<p>As previously certified Solution Focussed Coach, I know that as coach you should not
have any bias for any of your clients (or in my case colleagues), at all, ever. Period. Why? Well, if you have an opinion about the other already, how can you remain curious to the reasons of the other and find the solution for them within themselves? Having said that, everyone has certain bias&rsquo;es and it is time to set that aside, the world will become a better place if we are all curious to the other.</p>
<p>There the challenge comes in, because if you do not know what your personal strengths
and weaknesses are in real life, how can do know if you have a bias against something or someone?</p>
<h2 id="what-do-you-learn">What do you learn?</h2>
<p>Within the NLP Practitioner training of BDPTraining, you will learn yourself quite well. You will see things you always took for granted, and know you can throw it through the waste bin from now on. You will learn how your brains work, like where the hippocampus lies and that it is the cinema of your brains (amongst other uses the hippocampus has), how you can train your brain to become more resourceful. You will do practical hands on training with other curious people that become your friends in relatively short time. You see yourself struggling and knowing that others struggle there too.</p>
<p>One give away that I&rsquo;ll give you from this post: problems only exist in your head, no one else has your problems. So stop doing them in your head and get rid of them. If someone else has a problem, it is up to you to accept it (and have the problem as well), or just keep it with the other.. I know what I prefer to do!</p>
<h2 id="so-what-is-next">So what is next?</h2>
<p>The course was about 7 blocks, divided over 4 months. After block 1 I mentioned at home that I should do the masters as well. I will continue practising on a daily basis whenever a situation occurs that I can use my skills in. Not in an obsessive damaging way, but in a way to connect to the other person better.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This is one of the longest courses that I took, one of the most intensive courses that I ever had. But also the course that really challenged me and made me curious about other topics regarding the brain and human activity around it. I am pondering several educations that strengthen this knowledge even further. I am not going to promise that I will be doing them, but I am investigating.</p>
<p>Oh, and I found this one of the best courses I ever had, that really enriched me as person, improves me as husband and father, enables me as manager and coach, makes me a better friend, and hopefully others agree with that, and else.. not my problem&hellip;</p>
]]></content:encoded></item><item><title>In Memoriam Puc</title><link>/posts/2025-11-25-in-memoriam-puc/</link><pubDate>Tue, 25 Nov 2025 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2025-11-25-in-memoriam-puc/</guid><description>&lt;img src='https://www.evilcoder.org/images/puc.png' /&gt;
&lt;p&gt;Dear Puc,&lt;/p&gt;
&lt;p&gt;I am not a big fan of writing about personal items, for you I make an
exception. You changed my world. I deeply miss you already.&lt;/p&gt;
&lt;p&gt;Since 2014 you had been our companion. Our first dog, our sweetest dog.
Named after &amp;ldquo;Pug&amp;rdquo; the magician from Raymond E. Feist, you were our pride.
You came to our house almost 11 years ago to the date (23rd of november
2014). We were so proud to bring you back home. We redecorated the house
for you with rugs all over the bottom floor. When we came home, or downstairs
you were there waiting for us, impatiently and happy to see us, always
without any opinions, just happy and in for a cuddle!&lt;/p&gt;</description><content:encoded><![CDATA[<img src='/images/puc.png' />
<p>Dear Puc,</p>
<p>I am not a big fan of writing about personal items, for you I make an
exception. You changed my world. I deeply miss you already.</p>
<p>Since 2014 you had been our companion. Our first dog, our sweetest dog.
Named after &ldquo;Pug&rdquo; the magician from Raymond E. Feist, you were our pride.
You came to our house almost 11 years ago to the date (23rd of november
2014). We were so proud to bring you back home. We redecorated the house
for you with rugs all over the bottom floor. When we came home, or downstairs
you were there waiting for us, impatiently and happy to see us, always
without any opinions, just happy and in for a cuddle!</p>
<p>Immediately from the start you took our hearts and were part of the family.
You played with our son Bram, took his socks from him, and had great joy
doing so. Obviously Bram found something of that and came after you to get
back his socks. You just ran and had fun with him.</p>
<p>With Denise you made long walks when she was at home before and after her
pregnancy, you were with her and sat next to her when she needed a rest.
You both made many miles! You were a real help to her and always there
for her. She trained you, you were so eager to learn always, and to work
for your two biggest gifts: a cookie, and the most important one, hugs!</p>
<p>With Luca you had a very sweet moment, when he came back from a school camp
you ran to him (we could not hold you), and jumped on him placing your legs
around his neck and gave him a big hug. You missed him for sure! It was a
nice moment to see and we will always remember it (we have the pictures).</p>
<p>When our youngest child, Julia, was born, you welcomed her with a lick on
her feet, and sat next to her, protecting her as it was your own child.
She could do anything with you, from horse riding to just lay with you on
the couch, you found it all OK. She still mentions that she was your
biggest friend.</p>
<p>You were naughty, energetic, very sweet, attentative, and stole the hearts
of everyone you have ever met. You had an ever lasting grin on your face
showing that you were relaxed, and happy. You had that for almost your
entire life! You did not let go of people easily, you had to hug them
and they needed to pet you. With those things, you really changed a lot
of lifes!</p>
<p>Since you were part of our family, you were never away from us more then
a day. Someone was always close to you, you went with us on holidays,
especially the ones on Texel stood out, where you had seen the ocean
for the first time, and we could barely hold you because this is what
you wanted! And we gave it to you a few times. You also liked to jump
in the ice cold waters in Belgium, sit with us in the bath in Groningen,
walk through the forests in Germany (you actually took me up the hills
you know, with your strength and enthusiasm to see what was behind the
top).</p>
<p>You were never afraid of the fireworks, we helped you in your younger
years to not be scared, and till the last fireworks sparked, you were
never ever afraid of it.</p>
<p>You learned that when we said: &ldquo;Run&rdquo; you took your leash in your mouth
and started to run as hard as you could. You outran everyone easily
and then you just ran back and forth. You had so much energy always and
always wanted to come along outside. You loved traveling in the car,
you were already in the car sometimes before the blink of an eye.
As if you wanted to say: I am there, I am waiting for you, lets go!</p>
<p>Swiming was a big passion of you, you once took a swim and almost crossed
to the other end of the &ldquo;Waal&rdquo;, I got instructed to drop my clothes and
swim after you, because it didn&rsquo;t look like you wanted to return. After some
calling and waiting patiently, you returned to the end we were on. As if you
wanted to say: I just inspected the other end, it was fun and now I am back.</p>
<p>You also were always with us in the house, when we were in the kitchen, you
were there as well. Not in the most convient place ofcourse, but visible,
feelable and well, you could not be missed. If friends come over to play
a game, you were under the table, lying closely to them or even on their
feet. If the weather was hot outside, you lay on the loungechairs, like a
princess, unable to miss you. Or when the kids had the pool setup, you wanted
to play with the water as well. A water playing game? you were in it!</p>
<p>I become quite ill in 2016, I could barely walk and my world was spinning
around constantly. I could not do much, but the one thing that I had to do
frequently, is walk with you. I heard from the physians, that normally elderly
who have this, recover in +/- 2 years. You helped me recover in a year.
When I walked all around the roads and almost walked into the creeks, you were
next to me, preventing me from tripping in, and going around all those people
that thought I was drunk. I was not, but my illness made it look like it.
You were the supporting one always.</p>
<p>In that period, our cat Max came to your life, you could not get any
puppies, but still for some reason you had to change someones life again.
You began to give milk, and Max drank this from you. The both of you
walked happily outside together. Max always hid from you and then
jumped from behind a tree or something and you always got scared a bit.
And then happily continued your journey. it is a thing we will never forget!</p>
<p>During the period after, you slowly developed artrosis, we tried a lot
of medications, but somehow, it did not actually help that much. What
helped is with the help of Natasha, an animal chiropractor, you could
do many more things, even without medication. What a life safer!</p>
<p>2 years ago, we adopted a second dog, Saar. You strongly and surely
helped to raise her. You played with her, eventhough it was not easy
for you sometimes, and helped her when she did things that were not
allowed. Then a year ago, Lotje came into our life, and you helped
her grow up as well.</p>
<p>But we also saw you slowly taking steps back.
You became more and more held back, you started sleeping more often
and a few weeks ago you started drinking more and more, and also peeing
more. You could not handle the two young dogs and showed your boundaries
much quicker (you did not actually have them at all!).</p>
<p>Looking back, the period that you became worse, grew quicker then we
imagined. We decided to take you to the vets to see what appeared to be
not entirely you anymore. The grin on your face was gone, you seemed
tired, and sad. The verdict was quite surprising, you looked healthy
from the outside, except that you could not stand easily, which was
likely due to the artrosis. But we decided to do a blood test as well.</p>
<p>Unexpectedly, the result was that you were severe diabetic. We learned
that on friday, and we needed to decide what to do next. With a lot of
grief we decided that we should let you go. Not because we wanted to,
but we saw that we could never help you get back to who you were.
Doing a very intensive support, would have made your life more difficult
as well.</p>
<p>And now I write this &rsquo;letter&rsquo; to you as a rememberance, you, who I
and we all in the family loved dearly.</p>
<p>Dear Puc, you had a great heart, and with heavy heart, a lot of love
and tears, we had let you go. We were all with you, Julia, Bram, Luca,
Denise, Saar, Lotje and myself. We supported you on the most important
trip you were ever going to make, without us.</p>
<p>You will be in our hearts forever, sleep well my dearly beloved friend
that was always happy when I or we come home whenever time it was at the
day. We miss you! We love you!</p>
<p>Rest in peace, we will meet again!
Remko &amp; fam</p>
]]></content:encoded></item><item><title>VMUG Netherlands</title><link>/posts/2025-03-12-vmug/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2025-03-12-vmug/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;When I worked for Snow, I once a year visited the BSD Conferences and went
to BSDCan one time. There are equal kind of conferences for VMware related
groups, like the VMware User Group (VMUG).&lt;/p&gt;
&lt;p&gt;This year the VMUG-NL Conference had been hosted in Den Bosch, in the 1391
venu.&lt;/p&gt;
&lt;h2 id="how-was-it"&gt;How was it?&lt;/h2&gt;
&lt;p&gt;It was good to see fellow colleague&amp;rsquo;s heading over there, you talk about different
things then at work and in general you have fun together.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>When I worked for Snow, I once a year visited the BSD Conferences and went
to BSDCan one time. There are equal kind of conferences for VMware related
groups, like the VMware User Group (VMUG).</p>
<p>This year the VMUG-NL Conference had been hosted in Den Bosch, in the 1391
venu.</p>
<h2 id="how-was-it">How was it?</h2>
<p>It was good to see fellow colleague&rsquo;s heading over there, you talk about different
things then at work and in general you have fun together.</p>
<p>The venue is more then large enough, with enough stands and facilities
including food and beverages. The food was partially processed locally
as we were told by one of the hostess.</p>
<p>Also, somewhere I saw an old colleague from my time at a government
manicupality walking around.</p>
<p>I found several good presentations, the opening was interesting, the
talk from Pure Storage was interesting:</p>
<ul>
<li>Pure Storage Business Continuity and Disaster Recovery of VMware Private Cloud</li>
</ul>
<p>Several sessions from VMware regarding the future of VCF and vSAN were not
that great. The midday keynote was a bit over the top. I think the content
itself was great but the showplay around it was just not needed. We even left
early because of that. That included the following:</p>
<ul>
<li>VMware&rsquo;s vision for storage and data protection in vSAN and VCF 9</li>
<li>vSAN in VCF Operations: monitoring and performance troubleshooting</li>
<li>Disaster Recovery in the Broadcom world: Setup, Configure &amp; Manage</li>
</ul>
<p>The best presentation of the day was</p>
<ul>
<li>Big Game Hunting: Ransomware’s High-Stakes War on Enterprises</li>
</ul>
<p>Overall it was a nice day to see and meet fellow minded people. The organisation
did a great job in getting people together, sometimes things do not entirely
come out as expected, but that could be more a pointer to the presenters.</p>
<p>Who, admittedly stood there, and I was not, so they have a point ahead :-)</p>
]]></content:encoded></item><item><title>Parallel runs of ansible 'stuff'.</title><link>/posts/2024-11-22-parallel-ansible-builds/</link><pubDate>Fri, 22 Nov 2024 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2024-11-22-parallel-ansible-builds/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;At work, we have a central configuration build, that we pick parts and
pieces from for our &amp;lsquo;deployments&amp;rsquo;. As you can read in my resume I am
working as a Virtualisation Engineer. There I am building most of the
deployment code. Our base structure looks at our vCenters as primary
key.&lt;/p&gt;
&lt;h2 id="what-does-that-mean"&gt;What does that mean?&lt;/h2&gt;
&lt;p&gt;Well, if you take a vCenter as main key, then everything that builds
a vCenter (ESXi hosts, clusters, distributed switches, storage, etc.)
are parts under the vCenter configuration. So you target a vCenter and
not specifically the hosts underneath a vCenter.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>At work, we have a central configuration build, that we pick parts and
pieces from for our &lsquo;deployments&rsquo;. As you can read in my resume I am
working as a Virtualisation Engineer. There I am building most of the
deployment code. Our base structure looks at our vCenters as primary
key.</p>
<h2 id="what-does-that-mean">What does that mean?</h2>
<p>Well, if you take a vCenter as main key, then everything that builds
a vCenter (ESXi hosts, clusters, distributed switches, storage, etc.)
are parts under the vCenter configuration. So you target a vCenter and
not specifically the hosts underneath a vCenter.</p>
<h2 id="yeah-so">Yeah so?</h2>
<p>That means, that you need to do inflexible loops to go over the storage
items, or ESXi hosts. Imagine that both storage and ESXi are address
since somehow they related, then you might need to do two loops to get
the implementation done. That is fine with a couple of hosts and small
storage, but if you need to do that en-masse, that is inflexible. You
cannot do easy loops as you can do in Python or Powershell for example.</p>
<p>So bottomline, this takes a bunch of time to process and when you deploy
something, you want it as quick as possible.</p>
<h2 id="to-the-rescue">To the rescue</h2>
<p>I do not refer to Ansible&rsquo;s Rescue mode. Block,rescue,always, you&rsquo;ll know
this if you done the course ;-). But I got a tip recently from one of my
colleague&rsquo;s from the Linux team, that there is this concept of &lsquo;virtual
host groups&rsquo; in Ansible. (ansible.builtin.add_host). So you can do
some loops from the configuration and build virtual host objects and
add them to a virtual group.</p>
<p>If you then rewrite parts of your &lsquo;sequential&rsquo; playbook into smaller
subsections and put them in an own play (basically still the same
but then started from a different play). You can target the virtually
created group in one go, which without limit just pushes it to as
many (virtual) host objects as possible.</p>
<p>So instead of sequentially looping over each ESXi host and then storage.
You can get all ESXi hosts, create the configuration for it that you need
put it in a virtual ESXi group, and run your play against it. If one of
the config items is a large configuration block for storage, you can then
loop over that, or if you restucture it smartly, you might be able to use
different &lsquo;primary keys&rsquo; to smash the data against. This saves at least one
slow iteration, and in my case it speeds up a large part of the building
blocks by a factor of 10 (reducing the implementation time hugely).</p>
<h2 id="how-does-that-look-like">How does that look like?</h2>
<p>Again I cannot show how we do that at work, but given a certain configuration
structure like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">configuration</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">cluster</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">clusterA</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">hosts</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">HostA</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ip</span>: <span style="color:#ae81ff">127.0.0.1</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">description</span>: <span style="color:#ae81ff">This is host A under cluster A</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">HostB</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ip</span>: <span style="color:#ae81ff">127.0.0.2</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">description</span>: <span style="color:#ae81ff">This is host B under cluster A</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">ClusterB</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">hosts</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">HostC</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ip</span>: <span style="color:#ae81ff">127.0.0.3</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">description</span>: <span style="color:#ae81ff">This is host C under cluster B</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">HostD</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">ip</span>: <span style="color:#ae81ff">127.0.0.4</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">description</span>: <span style="color:#ae81ff">This is host D under cluster B</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">storage</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">hosts</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">StorageA</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">fqdn</span>: <span style="color:#ae81ff">storage-a.your.domain</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">datastores</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">datastoreA</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">size</span>: <span style="color:#ae81ff">1GB</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">amount</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">datastoreB</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">size</span>: <span style="color:#ae81ff">10GB</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">amount</span>: <span style="color:#ae81ff">10</span>
</span></span></code></pre></div><p>You could have a playbook that has:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build storage</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">localhost</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">tasks</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Get all data</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ansible.builtin.debug</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">msg</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;storagename: {{ storage.0.name }}&#34;</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;datastorename: {{ storage.1.name }} with size: {{ storage.1.size }} and how many times {{ storage.1.amount }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop_control</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">loop_var</span>: <span style="color:#ae81ff">storage</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop</span>: <span style="color:#e6db74">&#34;{{ query(&#39;subelements&#39;, configuration.storage.hosts, &#39;datastores&#39;) }}&#34;</span>
</span></span></code></pre></div><p>This will result in:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>[<span style="color:#ae81ff">...]</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ok</span>: [<span style="color:#ae81ff">localhost] =&gt; (item=[{&#39;name&#39;: &#39;StorageA&#39;, &#39;fqdn&#39;: &#39;storage-a.your.domain&#39;}, {&#39;name&#39;: &#39;datastoreA&#39;, &#39;size&#39;: &#39;1GB&#39;, &#39;amount&#39;: 10}]) =&gt; {</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;msg&#34;: </span>[
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;storagename: StorageA&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;datastorename: datastoreA with size: 1GB and how many times 10&#34;</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#f92672">ok</span>: [<span style="color:#ae81ff">localhost] =&gt; (item=[{&#39;name&#39;: &#39;StorageA&#39;, &#39;fqdn&#39;: &#39;storage-a.your.domain&#39;}, {&#39;name&#39;: &#39;datastoreB&#39;, &#39;size&#39;: &#39;10GB&#39;, &#39;amount&#39;: 10}]) =&gt; {</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;msg&#34;: </span>[
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;storagename: StorageA&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;datastorename: datastoreB with size: 10GB and how many times 10&#34;</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>But, if you need to do something with the hosts as well, you cannot navigate to that, because that is on a different level/path
in the configuration.</p>
<p>So you might need to do another loop and include a task file to target these hosts with the data from the loop above.</p>
<p>One can also get a list of all hosts, so if you add the following to the deploy yaml:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Get all nodes</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ansible.builtin.debug</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">msg</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;clustername: {{ cluster.0.name }}&#34;</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;hostname: {{ cluster.1.name }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop_control</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">loop_var</span>: <span style="color:#ae81ff">cluster</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop</span>: <span style="color:#e6db74">&#34;{{ query(&#39;subelements&#39;, configuration.cluster, &#39;hosts&#39;) }}&#34;</span>
</span></span></code></pre></div><p>Then you will also have a list of clusters and nodes underneath that cluster.</p>
<p>If you then take the data and create a specific hostconfiguration (below is a dummy, you should be
able to see the vision behind it, or contact me if not ;-)):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build storage</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">localhost</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">tasks</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Get all data</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ansible.builtin.debug</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">msg</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;storagename: {{ storage.0.name }}&#34;</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;datastorename: {{ storage.1.name }} with size: {{ storage.1.size }} and how many times {{ storage.1.amount }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop_control</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">loop_var</span>: <span style="color:#ae81ff">storage</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop</span>: <span style="color:#e6db74">&#34;{{ query(&#39;subelements&#39;, configuration.storage.hosts, &#39;datastores&#39;) }}&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Get all nodes</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ansible.builtin.debug</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">msg</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;clustername: {{ cluster.0.name }}&#34;</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;hostname: {{ cluster.1.name }}&#34;</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#e6db74">&#34;storagedata: {{ configuration.storage.hosts }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop_control</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">loop_var</span>: <span style="color:#ae81ff">cluster</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop</span>: <span style="color:#e6db74">&#34;{{ query(&#39;subelements&#39;, configuration.cluster, &#39;hosts&#39;) }}&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Add virtual hostgroup</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ansible.builtin.add_host</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">groups</span>: <span style="color:#e6db74">&#39;virtual_hostgroup&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;{{ cluster.1.name }}&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">cluster_name</span>: <span style="color:#e6db74">&#34;{{ cluster.0.name }}&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">storagedata</span>: <span style="color:#e6db74">&#34;{{ configuration.storage.hosts }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop_control</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">loop_var</span>: <span style="color:#ae81ff">cluster</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">loop</span>: <span style="color:#e6db74">&#34;{{ query(&#39;subelements&#39;, configuration.cluster, &#39;hosts&#39;) }}&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## New play only targeting the host objects</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build storage for host</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hosts</span>: <span style="color:#ae81ff">virtual_hostgroup</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">gather_facts</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">tasks</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Print host</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">ansible.builtin.debug</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">msg</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#e6db74">&#34;{{ inventory_hostname }}&#34;</span>
</span></span><span style="display:flex;"><span>          - <span style="color:#e6db74">&#34;storages: {{ storagedata }}&#34;</span>
</span></span></code></pre></div><p>This will give the output of:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ae81ff">TASK [Print host] ***********************************************************************************************************************************************************************************************************************</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">task path</span>: <span style="color:#ae81ff">demo.yaml:42</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ok</span>: [<span style="color:#ae81ff">HostA] =&gt; {</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;msg&#34;: </span>[
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;HostA&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;storages: [{&#39;name&#39;: &#39;StorageA&#39;, &#39;fqdn&#39;: &#39;storage-a.your.domain&#39;, &#39;datastores&#39;: [{&#39;name&#39;: &#39;datastoreA&#39;, &#39;size&#39;: &#39;1GB&#39;, &#39;amount&#39;: 10}, {&#39;name&#39;: &#39;datastoreB&#39;, &#39;size&#39;: &#39;10GB&#39;, &#39;amount&#39;: 10}]}]&#34;</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#f92672">ok</span>: [<span style="color:#ae81ff">HostB] =&gt; {</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;msg&#34;: </span>[
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;HostB&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;storages: [{&#39;name&#39;: &#39;StorageA&#39;, &#39;fqdn&#39;: &#39;storage-a.your.domain&#39;, &#39;datastores&#39;: [{&#39;name&#39;: &#39;datastoreA&#39;, &#39;size&#39;: &#39;1GB&#39;, &#39;amount&#39;: 10}, {&#39;name&#39;: &#39;datastoreB&#39;, &#39;size&#39;: &#39;10GB&#39;, &#39;amount&#39;: 10}]}]&#34;</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#f92672">ok</span>: [<span style="color:#ae81ff">HostC] =&gt; {</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;msg&#34;: </span>[
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;HostC&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;storages: [{&#39;name&#39;: &#39;StorageA&#39;, &#39;fqdn&#39;: &#39;storage-a.your.domain&#39;, &#39;datastores&#39;: [{&#39;name&#39;: &#39;datastoreA&#39;, &#39;size&#39;: &#39;1GB&#39;, &#39;amount&#39;: 10}, {&#39;name&#39;: &#39;datastoreB&#39;, &#39;size&#39;: &#39;10GB&#39;, &#39;amount&#39;: 10}]}]&#34;</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#f92672">ok</span>: [<span style="color:#ae81ff">HostD] =&gt; {</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;msg&#34;: </span>[
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;HostD&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;storages: [{&#39;name&#39;: &#39;StorageA&#39;, &#39;fqdn&#39;: &#39;storage-a.your.domain&#39;, &#39;datastores&#39;: [{&#39;name&#39;: &#39;datastoreA&#39;, &#39;size&#39;: &#39;1GB&#39;, &#39;amount&#39;: 10}, {&#39;name&#39;: &#39;datastoreB&#39;, &#39;size&#39;: &#39;10GB&#39;, &#39;amount&#39;: 10}]}]&#34;</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Where you then can do a loop over the storagedata, or more complex data, but do it in parallel for each host (instead of sequentially per host). You can also only put in the information that is needed for this run and send them along as host_vars.</p>
<p>Ofcourse our setup is much much much more complex and has a lot more data, so it is not comparable at all. But, at least this gives
an idea how you can target something like that. You could also in the above examples use the storage as primary key, and then do
something with that when you loop over the hosts (the other way around then this example). It is all depending on what you need and
how you need it. There might be 1000&rsquo;s of hosts, 1000&rsquo;s of datastores, 1000&rsquo;s of whatever, and combining this wisely make you
capable of doing things more in parallel instead of:</p>
<p>loop over all hosts (*1000)
loop over all datastores (*1000)
loop over all whateverdata (*1000)</p>
<p>you do:</p>
<p>1000*host
loop over all datastores(*1000)
loop over all whateverdata (*1000)</p>
<p>you can do the first run in parallel and take out a sequential wait of &lsquo;1000&rsquo;, and if every host iteration takes a second
that saves you 1000 seconds or just shy of 17 minutes.</p>
<p>I did not experiment with this, but you might be able to create secondary virtual hostgroups and in parallel attack the
datastores as well (play with forks or serial to prevent overloading your system ;-), reducing the time even more.</p>
<h3 id="summary">Summary</h3>
<p>For us this is a huge performance gain where we can target the things that take a lot of time, and combine the data into a
virtual host object in a virtual hostgroup, and target that in a seperate play and do activities on them in parallel.</p>
<p>As always, if you have questions, please contact me.</p>
]]></content:encoded></item><item><title>Mermaid and Pydantic</title><link>/posts/2024-10-01-mermaid-and-pydantic/</link><pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2024-10-01-mermaid-and-pydantic/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In my previous article, I was writing about Mermaid and that I wanted to
experiment with generating documentation from the actual sources.&lt;/p&gt;
&lt;p&gt;I was recently able to focus properly on this and I think I had a blast
breakthrough at least for myself.&lt;/p&gt;
&lt;h2 id="the-state-now"&gt;The state now&lt;/h2&gt;
&lt;p&gt;Last time I wrote that I was still investigating how and what, especially
because of the compression that takes place when you do a graph TopDown(TD).
There is a very simple solution to that, make it Left to Right!&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In my previous article, I was writing about Mermaid and that I wanted to
experiment with generating documentation from the actual sources.</p>
<p>I was recently able to focus properly on this and I think I had a blast
breakthrough at least for myself.</p>
<h2 id="the-state-now">The state now</h2>
<p>Last time I wrote that I was still investigating how and what, especially
because of the compression that takes place when you do a graph TopDown(TD).
There is a very simple solution to that, make it Left to Right!</p>
<p>Now all I needed to add to the flavor is parse the bits and pieces of our
configuration data. As mentioned I use Ansible for that, I generate the
configuration I need and use a jinja template to actually parse the data
and print whatever I need wherever I need it. I cannot share details ofcourse
because that is work related, but I am sure you can use your imagination on
your own defined configuration and what items you need to graph something.</p>
<p>Oh, and I tossed the subgraphs entirely. I did have a look at the architecture
drawing that one can make, but that seems still a bit too difficult to get a
proper model out of that.</p>
<h2 id="model">Model</h2>
<p>I mentioned the word model in the previous section on purpose, our configuration
is repeatable, as any modern configuration should be when you use it a gazillion
times. That also means that you can wrap it in a model.</p>
<h2 id="pydantic">Pydantic</h2>
<p>This is where pydantic comes in, a coworker of mine did a demo of this recently
and I watched it afterwards (it was given on my day off, but we love to share
internally so I could still view it later on). He is from a different group but
they too have a configuration that is repeatable and perfectly fits a model.</p>
<h3 id="what-is-pydantic">What is Pydantic</h3>
<p>I gave a talk about Pydantic recently myself and I used the phrase:
It is a strict and quick validator of a given model, over a defined configuration.</p>
<p>Perhaps that does not give the right merit to the tool, since FastAPI uses it to
validate in and output on the fly with the tool, but for me this works perfectly.</p>
<h3 id="how-does-it-work">How does it work</h3>
<p>Basically you stricly denote your configuration, and if you for example use yaml
this has a certain layout. I will try to give an example a bit lower in the article.
This layout and configuration items (yaml entries, like lists, dicts, a combination
of them, etc.) if used well, are always matching a certain criteria.</p>
<p>Like with ansible, something can be &lsquo;state: present&rsquo;, or &lsquo;state: absent&rsquo;. If you
would wrap that in a Pydantic scheme, it will become: &lsquo;state: Literal[&ldquo;present&rdquo;,&ldquo;absent&rdquo;]&rsquo;.
That means, that if the validation traverses your configuration, and finds a state
keyword, it should match either present or absent. All other values are wrong and
your validator should fail. You can also have the flag: &rsquo;enabled: true&rsquo;, or false.
That reads in pydantic like: &rsquo;enabled: bool&rsquo;, since it is either true or false.
if it is an integer (all digits), then you can state: &lsquo;version: int&rsquo; for example.
You can use regular expressions as well, so if you know how a keyword&rsquo;s value
should look like, you can push it through a regular expression and validate that
what you think must be defined is actually defined.</p>
<p>but, not everything is Required right ? That is true, so using the version as
example, if that is an optional parameter in your configuration, you can
define that like &lsquo;version: int | None = None&rsquo;, and it will be either if it
exists an integer, or ignored (/optional) it is is not defined.</p>
<h3 id="sections">Sections</h3>
<p>So, not all configuration has just one layer, moest configuration has
lists, dicts, a combination of them, can you validate that as well? Yes you can.
You can point a certain part of your configuration, to an &lsquo;upstream&rsquo; validation.
So instead of telling that &lsquo;version: int&rsquo; is what should happen, imagine it is a
list. you can duplicate your codeblock and name it &lsquo;VersionCheck&rsquo; for example.
Then you do this in the lower config item: &lsquo;version: VersionCheck&rsquo;. You have
that new structure that is named VersionCheck ABOVE (bottom up thus) the normal
validator, and define how the version contents should be. Perhaps it shows like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">version</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">This is our version</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">major</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">minor</span>: <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">patch</span>: <span style="color:#ae81ff">p0</span>
</span></span></code></pre></div><p>That does not work if you tell &lsquo;version: int&rsquo; right?
So imagine you created that new VersionCheck, you can then point version to that
validation object, and do this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">str</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">major</span>: <span style="color:#ae81ff">int</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">minor</span>: <span style="color:#ae81ff">int</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">patch</span>: <span style="color:#ae81ff">str (or regex that ^p\d is what you expect).</span>
</span></span></code></pre></div><p>The version tag is actually missing, because you &lsquo;decent&rsquo; into the version
hierarchy when you reference it. That way you can loop over lists, dicts,
etc pretty easily.</p>
<h3 id="how-did-you-implement-this">How did you implement this?</h3>
<p>I cheated a bit and read his model and adopted it to our configuration and we
optimized it a bit to use it in our CI/CD stream. I use Ansible (yes again)
to construct the configuration that I modified my colleague&rsquo;s wrapper for and
use that to parse the data.</p>
<p>I cannot share details on how we did that at work, but if you are really curious
I am considering writing a post on it, so that you can have an idea.</p>
]]></content:encoded></item><item><title>Azure Devops - Mermaid</title><link>/posts/2024-02-27-azure-devops-mermaid/</link><pubDate>Tue, 27 Feb 2024 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2024-02-27-azure-devops-mermaid/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In a previous article, I wrote something about Azure Devops and how I see it.
I also wanted to play with automated documentation generation, which basically
means that I am considering a template, and get bits and pieces from a configuration
that I also use to deploy stuff, into a markdown file and present that on a &amp;lsquo;host&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;One of the things that &amp;lsquo;annoyed&amp;rsquo; me is that documentation always lags behind.
Why? Because you need to modify it to remain relevant. See it is an instruction
manual for your car, as long as the car itself remains the same, the manual
remains relevant. But if you lets say, change the navigation unit in it, you
either need to write a new part for that in the documentation (and add it as
addendum or something), or reprint the instructions, else it will not fit anymore.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In a previous article, I wrote something about Azure Devops and how I see it.
I also wanted to play with automated documentation generation, which basically
means that I am considering a template, and get bits and pieces from a configuration
that I also use to deploy stuff, into a markdown file and present that on a &lsquo;host&rsquo;.</p>
<p>One of the things that &lsquo;annoyed&rsquo; me is that documentation always lags behind.
Why? Because you need to modify it to remain relevant. See it is an instruction
manual for your car, as long as the car itself remains the same, the manual
remains relevant. But if you lets say, change the navigation unit in it, you
either need to write a new part for that in the documentation (and add it as
addendum or something), or reprint the instructions, else it will not fit anymore.</p>
<p>This requires manual labor. And a part of my job is to automate our deployments.
Manual, automate, that does not compute right? It does in some context, but in
this particular context it does not.</p>
<p>I wanted to experiment with a system that would auto rebuild the documentation
based on the actual sources. Since we run an Ansible environment, we have a
codebase and configuration in place. Why not use them together and extract the
bits and pieces we need?</p>
<h2 id="what-i-found-initially">What I found initially</h2>
<p>Is that my predecessor, thanks Liam, already took care of a couple of those
things. He wrote playbooks that extract a couple of configuration parameters
and transform that into a temporary Markdown file and release that as artifact
which gets used before publishing the wiki for example.</p>
<p>That gave me a nice boost, practically visible on what is possible, I decided
to start the experiment there. But that is mainly text? What about graphics?
Mermaid to the rescue!</p>
<h2 id="mermaid">Mermaid</h2>
<p>I saw, I think a year ago, a mentioning of mermaidjs thing from a colleague and
also saw it in Joplin, and then at various other places. Back then I did not take
particular note of it, as I was busy making automated deployments for our infrastructure.</p>
<p>Now that that dust settled, and I was going to experiment with automated documentation
I searched back in my mind, and recalled Mermaid. What does it do?</p>
<h3 id="what-is-mermaid">What is mermaid?</h3>
<p>Mermaid is basically a diagramming and/or charting tool as you wish. With a simple
set of instructions, you can create a diagram from text based input. Awesome right?
To give a little example, in markdown you could specify this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#e6db74">```mermaid
</span></span></span><span style="display:flex;"><span>  flowchart TD
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  Topitem-&gt;Secondlayer_1
</span></span><span style="display:flex;"><span>  Topitem-&gt;Secondlayer_2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  Secondlayer_1 --&gt; Bottomlayer_1_1
</span></span><span style="display:flex;"><span>  Secondlayer_1 --&gt; Bottomlayer_1_2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  Secondlayer_2 --&gt; Bottomlayer_2_1
</span></span><span style="display:flex;"><span>  Secondlayer_2 --&gt; Bottomlayer_2_2
</span></span><span style="display:flex;"><span><span style="color:#e6db74">```</span>
</span></span></code></pre></div><pre class="mermaid">  flowchart TD

  Topitem --> Secondlayer_1
  Topitem --> Secondlayer_2

  Secondlayer_1 --> Bottomlayer_1_1
  Secondlayer_1 --> Bottomlayer_1_2

  Secondlayer_2 --> Bottomlayer_2_1
  Secondlayer_2 --> Bottomlayer_2_2
</pre>

<p>This is ofcourse all very basic, the mermaid processor can work with &lsquo;ids&rsquo; that
you can give a name, and use a large set of different graphical layouts as well as use
markup to make clear what the id means.  See <a href="https://mermaid.js.org/syntax/flowchart.html">here</a>
for more information on how this could work for you.</p>
<h3 id="and-now-what">And now what?</h3>
<p>Well, did I mention that I use &lsquo;draw.io&rsquo; when I want to make a drawing and/or diagram?
Do you notice the overlap there ? Most often a drawing of how an enviroment looks like
is nothing more then a Diagram with some markup. Machine A connects to Network A with
IP A and B on both sides, it uses protocol XYZ between them to talk to eachother. They
have a certain storage backend to storage host A, etc. That is just a flowchart presented
differently. In my eyes then. Send me a message in case you disagree or see it differently.</p>
<p>So. We have this annoying thing that you need to update documentation when you change
something, which most often is still required, but also some parts that you can extract
from &lsquo;your configuration&rsquo; and update automatically. Like a drawing of the environment!
And you know as I do, that drawings are most often updated last, and/or just forgotten.</p>
<p>I started to experiment with a hardcoded diagram, that looks a bit like the above example
but then more related to our environment.</p>
<h3 id="my-environment">My environment</h3>
<p>In a VMWare environment you normally have a vCenter, one or more clusters, and each cluster
has one or more ESXi hosts. Those hosts have some sort of storage layer between them,
specific network and vlans assigned to them etc. I combined that data in a diagram.
And it looked like a nice start, but not really there yet. In a small scale setup this
would make a network diagram automatically, and means you can automate the network
drawings away for these infrastructure components (not limited to though!). But we dont
have a small scale setup. We have quite a large estate running. This made it quickly
difficult to read.</p>
<p>From within DrawIO I would do something like the following to present this
(strongly simplified, and condensed, you get the drill):</p>
<p><img loading="lazy" src="/images/vCenter_demo.png" type="" alt="vCenter Demo"  title="vCenter Demo"  /></p>
<h3 id="subgraphs">Subgraphs?</h3>
<p>Because it became difficult to read if you add enough (valuable) data. I tried using sub
graphs, so I created a vCenter on top, linked that to a box with hosts on it, linked it
to a storage backend, linked to a network with a couple of vlans (distributed switch
reference in case you are familiar).</p>
<p>Roughly this looks like:</p>
<pre class="mermaid">
flowchart TD;

  subgraph A_vCenter
  vCenter --> A_Cluster
  vCenter --> A_Storage
  vCenter --> A_Network
  end

  subgraph A_Cluster
  Cluster_A --> Host_A
  Cluster_A --> Host_B
  Cluster_A --> Host_C
  end

  subgraph A_Storage
  Storage_A --> Volume_A
  Storage_A --> Volume_B
  end

  subgraph A_Network
  Network_A --> Vlan_A_123
  Network_A --> Vlan_B_456
  end
</pre>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#e6db74">```mermaid
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>flowchart TD;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_vCenter
</span></span><span style="display:flex;"><span>  vCenter --&gt; A_Cluster
</span></span><span style="display:flex;"><span>  vCenter --&gt; A_Storage
</span></span><span style="display:flex;"><span>  vCenter --&gt; A_Network
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_Cluster
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; Host_A
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; Host_B
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; Host_C
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_Storage
</span></span><span style="display:flex;"><span>  Storage_A --&gt; Volume_A
</span></span><span style="display:flex;"><span>  Storage_A --&gt; Volume_B
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_Network
</span></span><span style="display:flex;"><span>  Network_A --&gt; Vlan_A_123
</span></span><span style="display:flex;"><span>  Network_A --&gt; Vlan_B_456
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span><span style="color:#e6db74">```</span>
</span></span></code></pre></div><p>This is still readable in the above example, but if you have lets say 500 vlans connected to a cluster, just
because you can, it comes difficult to read. If you have multiple clusters under a vCenter, it comes difficult
to read, especially if you want to connect a cluster to <code>A_Storage</code> and another one to <code>B_Storage</code>
and perhaps want to mention which interfaces they use as the line-text. To give an example, I tried drawing that
below:</p>
<pre class="mermaid">
flowchart TD;

  subgraph A_vCenter
  vCenter --> A_Cluster
  vCenter --> B_Cluster
  end

  subgraph A_Cluster
  Cluster_A --> Host_A_A(Host_A_A)
  Cluster_A --> Host_A_B(Host_A_B)
  Cluster_A --> Host_A_C(Host_A_C)
  Cluster_A --> A_Storage
  Cluster_A --> A_Network
  end

  subgraph B_Cluster
  Cluster_B --> Host_B_A
  Cluster_B --> Host_B_B
  Cluster_B --> Host_B_C
  Cluster_B --> B_Storage
  Cluster_B --> B_Network
  end

  subgraph A_Storage
  Storage_A --> Volume_A_A[(Volume_A_A)]
  Storage_A --> Volume_A_B[(Volume_A_B)]
  end

  subgraph B_Storage
  Storage_B -->|HBA_B_A_1| Volume_B_A[(Volume_B_A)]
  Storage_B -->|HBA_B_A_2| Volume_B_B[(Volume_B_B)]
  end

  subgraph A_Network
  Network_A --> Vlan_A_A_123
  Network_A --> Vlan_A_B_456
  end
  subgraph B_Network
  Network_B -->|Iface_B_A_1| Vlan_B_A_1
  Network_B -->|Iface_B_A_2| Vlan_B_A_2
  Network_B -->|Iface_B_A_3| Vlan_B_A_3
  Network_B -->|Iface_B_A_4| Vlan_B_A_4
  Network_B -->|Iface_B_A_5| Vlan_B_A_5
  Network_B -->|Iface_B_A_456| Vlan_B_B_456
  end
</pre>

<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#e6db74">```mermaid
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>flowchart TD;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_vCenter
</span></span><span style="display:flex;"><span>  vCenter --&gt; A_Cluster
</span></span><span style="display:flex;"><span>  vCenter --&gt; B_Cluster
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_Cluster
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; Host_A_A(Host_A_A)
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; Host_A_B(Host_A_B)
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; Host_A_C(Host_A_C)
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; A_Storage
</span></span><span style="display:flex;"><span>  Cluster_A --&gt; A_Network
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph B_Cluster
</span></span><span style="display:flex;"><span>  Cluster_B --&gt; Host_B_A
</span></span><span style="display:flex;"><span>  Cluster_B --&gt; Host_B_B
</span></span><span style="display:flex;"><span>  Cluster_B --&gt; Host_B_C
</span></span><span style="display:flex;"><span>  Cluster_B --&gt; B_Storage
</span></span><span style="display:flex;"><span>  Cluster_B --&gt; B_Network
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_Storage
</span></span><span style="display:flex;"><span>  Storage_A --&gt; Volume_A_A[(Volume_A_A)]
</span></span><span style="display:flex;"><span>  Storage_A --&gt; Volume_A_B[(Volume_A_B)]
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph B_Storage
</span></span><span style="display:flex;"><span>  Storage_B --&gt;|HBA_B_A_1| Volume_B_A[(Volume_B_A)]
</span></span><span style="display:flex;"><span>  Storage_B --&gt;|HBA_B_A_2| Volume_B_B[(Volume_B_B)]
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  subgraph A_Network
</span></span><span style="display:flex;"><span>  Network_A --&gt; Vlan_A_A_123
</span></span><span style="display:flex;"><span>  Network_A --&gt; Vlan_A_B_456
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span>  subgraph B_Network
</span></span><span style="display:flex;"><span>  Network_B --&gt;|Iface_B_A_1| Vlan_B_A_1
</span></span><span style="display:flex;"><span>  Network_B --&gt;|Iface_B_A_2| Vlan_B_A_2
</span></span><span style="display:flex;"><span>  Network_B --&gt;|Iface_B_A_3| Vlan_B_A_3
</span></span><span style="display:flex;"><span>  Network_B --&gt;|Iface_B_A_4| Vlan_B_A_4
</span></span><span style="display:flex;"><span>  Network_B --&gt;|Iface_B_A_5| Vlan_B_A_5
</span></span><span style="display:flex;"><span>  Network_B --&gt;|Iface_B_A_456| Vlan_B_B_456
</span></span><span style="display:flex;"><span>  end
</span></span><span style="display:flex;"><span><span style="color:#e6db74">```</span>
</span></span></code></pre></div><p>And this is still very minimal, in a regular setup, you have many more details that
you might want to add. For now I am playing with the idea to generate multiple pages
of data and zoom in om a specific set of data per page render. This doesn&rsquo;t automate
away the generation of always up to date environment drawings though.</p>
<h3 id="manually">Manually</h3>
<p>Also this is still all done manually, which is also not the idea. What I want to do
is write an Ansible playbook, or re-use existing ones, and grab data from it and use
the ID and Name of a parameter to form the documentation and create that before
the artifacts are generated and used to generate the Wiki.</p>
<h3 id="briljant-now-that-works-we-have-finally-a-automated-network-drawing">Briljant, now that works we have finally a automated network drawing?</h3>
<p>No not really, the above examples are using the plain mermaid engine in for example
GoHugo. See <a href="https://gohugo.io/content-management/diagrams/">Here how to do that</a>.
But.. as more often Azure does a different thing. You can render a more limited
subset of the Mermaid application within Azure, using the :::mermaid code block.
This looks similar to the <code>```</code> blocks from Markdown, but isn&rsquo;t entirely
the same. Also it appears that you cannot use all features, nor multiple in a row.
So the above examples will not be possible within Azure at this moment. Having
said that, I also heard that the feature was much more limited before so it got
traction anyway. I hope it will be build out to a much more full set of the
regular mermaid implementation and that you can also use it similar to the examples
on the web. It would make life much easier for a lot of people. Note that the above
examples are, as far as I know and could test, usable, just limited to one per page.</p>
<p>If you have ideas about this, and/or would like to discuss this, you know where
to find me (See the contact page on top).</p>
]]></content:encoded></item><item><title>Azure Devops</title><link>/posts/2023-04-02-azure-devops/</link><pubDate>Mon, 24 Apr 2023 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2023-04-02-azure-devops/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Azure Devops is the &amp;lsquo;Github&amp;rsquo; of Microsoft basically. It contains container registries,
pipelines, git repositories, an test framework, sprint/task boards and many more things.
For many companies this is the defacto standard when it comes to doing DevOps based work.&lt;/p&gt;
&lt;p&gt;Lately I have been working a lot with the Azure Devops Git/Pipeline options within Azure
DevOps and must come to the conclusion, that a lot of the things we are using, are not
easy to find in the courses online. For this I tried to combine the options that I use
in this blog post. This blogpost will be periodically updated when I found out new
things, so that this combines all my knowledge in this region. I will probably address
the newer items in a seperated blog entry as well.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Azure Devops is the &lsquo;Github&rsquo; of Microsoft basically. It contains container registries,
pipelines, git repositories, an test framework, sprint/task boards and many more things.
For many companies this is the defacto standard when it comes to doing DevOps based work.</p>
<p>Lately I have been working a lot with the Azure Devops Git/Pipeline options within Azure
DevOps and must come to the conclusion, that a lot of the things we are using, are not
easy to find in the courses online. For this I tried to combine the options that I use
in this blog post. This blogpost will be periodically updated when I found out new
things, so that this combines all my knowledge in this region. I will probably address
the newer items in a seperated blog entry as well.</p>
<h2 id="architectural-overview-of-the-blogpost">Architectural overview of the blogpost</h2>
<p>To make clear what all the different bits and pieces of Azure mean and to make it more
visual, I created a little diagram: 
  <img loading="lazy" src="https://www.evilcoder.org/images/Azure_Diagram.png" alt="Azure Diagram"  title="Azure Diagram"  /></p>
<p>The Diagram was created with DrawIO, and reflects a few items that will be explained later.
The yellow boxes are &ldquo;stages&rdquo;, they are filled with &ldquo;blue&rdquo; job boxes, a stage has one or
more jobs, and subsequently the green boxes form steps, the lowest but most important parts
in a job.</p>
<p>The Diagram also demonstrates the Azure cloud on the right handside, and &ldquo;Artifact&rdquo; stores,
they are &ldquo;one&rdquo; within the cloud, but not to clobber the drawing, I made three of them.
Effectively they are all the same!</p>
<p>In addition there are three agents referenced, two different types. Each stage / job runs on
it&rsquo;s own agent and can have different policies applied to them. We have the External Agents
(EA) and Internal Agents (IA) in this drawing to make clear why Artifacts can be helpful.</p>
<p>Ofcourse all the information can be found on the internet, one of my main sources of information
for Azure DevOps is Microsoft itself, and there are many useful external references as well.
If you have comments, or want to discuss with me, see the contact menu item on top on how
to contact me. Do note that not every option from Azure DevOps is mentioned here, there are
far too many and I simply dont use everything, but I use a lot of the options available.</p>
<h2 id="pipelines">Pipelines</h2>
<p>What are pipelines actually? I always visualize the pipeline as a factory. Something
gets in (Resources) is being processed on various levels, and something gets out
(product). A good visualisation is likely a car factory. Some metals and required
resources get in, the framework gets build, doors, tires, electronics, etc etc. are
being added and optimized based on request, the car will be colored according to the
customer demands, and in the end a test will be concluded and the product delivered
to the end user.</p>
<p>All this, is largely done automatically inside the factory. If you explain that as
&lsquo;a pipeline&rsquo;, then you have an understanding on what a pipeline does.</p>
<p>You can also call it an advanced job scheduler if you are an old school Unix guy like
I am. In practise the pipeline orchestrates and enriches the jobs that need to run to
form the end result. This could be schedule based or trigger based on a commit (checkin)
to a repository, merge request, etc.</p>
<h2 id="explanation-about-pipelines-within-azure">Explanation about pipelines within Azure</h2>
<p>Like every vendor, the pipeline implementation all differs in some bits and pieces. If you
use the above example in your mindset, you can most likely extract the relevant data from
the product you are using. It could be that some of the options are named differently or
used differently. I myself have used Gitlab CI/CD to automate the delivery of these webpages
amongst, others, but also used Jenkins at my previous employer to generate periodic reports
and in the past I used this to deliver these webpages. At work we use Azure to deliver CI/CD
capabilities. All three have the same methodology but different implementations. Practise in
real life makes a difference so do that in case you are familiar with one product and not yet
with Azure.</p>
<h3 id="stages-jobs-steps">stages, jobs, steps</h3>
<h4 id="what-are-stages">What are stages</h4>
<p>You can see stages as as a set of &ldquo;jobs&rdquo; that form a coherent action.
For example if you have an upgrade process in place, you will probably
make a backup first, perform actions and update your configuration database
and cleanup the backup (if succeeded).</p>
<p>If you would create a high level drawing out of that, you would seperate them
in three steps likely: pre-actions, actions, post-actions. each of those
actions is a stage.</p>
<p>A stage can depend on another stage, imagine you need to prepare an image in
a stage, and later us that image to continue your deployment.
Selecting:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">stage</span>: <span style="color:#e6db74">&#34;Name_earlier_stage&#34;</span>
</span></span><span style="display:flex;"><span>  [<span style="color:#ae81ff">....]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">stage</span>: <span style="color:#e6db74">&#34;NameStage&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">dependsOn</span>: <span style="color:#e6db74">&#34;Name_earlier_stage&#34;</span>
</span></span></code></pre></div><p>will make your second stage dependent on your first stage.</p>
<h4 id="what-are-jobs">What are jobs</h4>
<p>As you could have read in the previous part about stages, a stage is divided
into a set of jobs. A job is a set of steps that form a logical entity as well.
In the above example, a job could be to do pre-actions. Imagine that this job
will do a couple of things (steps) before it finishes all these pre-actions.
In Unix you would likely call this a script, that has several functions or
actions taking place inside it.</p>
<p>In case of the pre-actions, you could do that into some smaller jobs like:</p>
<ul>
<li>do validation tests</li>
<li>make backup</li>
<li>store backup</li>
<li>update configuration database</li>
<li>set machine into maintenance (for alerting etc)</li>
<li>notify monitoring department or person on-call</li>
<li>etc.</li>
</ul>
<p>All these jobs have several actions taking place inside these jobs, we will get
to them in the next section.</p>
<h4 id="what-are-steps">What are steps</h4>
<p>Steps are the lowest part inside the Azure Pipeline, while on the bottom level
they are certainly one of the most important onces. Here actions actually take
place, where the stages and jobs form and combine the logical actions taking place
they do not hold actions themselves, the steps actually do something.</p>
<p>Imaging the above examples and we zoom in on the &lsquo;make backup&rsquo; job, that could have
several steps:</p>
<ul>
<li>Prepare environment
<ul>
<li>fetch keyvault secrets</li>
<li>install required dependencies</li>
<li>download company internal applications</li>
</ul>
</li>
<li>Login to the current machine to obtain token</li>
<li>Use token to call backup API</li>
<li>Download backup into staging directory</li>
<li>Upload artifact into the artifact store (either pipeline artifact, or Azure artifact)</li>
</ul>
<p>Each step runs on the agent that runs the job, but could have different tasks or scripts
that will be executed, like calling the API could be done with a Powershell script that
is used internally, but fetching the keyvault secrets could be a task provided by Azure
or company-wide.</p>
<h5 id="the-difference-between-tasks-and-scripts">The difference between tasks and scripts</h5>
<p>The difference is basically quite simple. A task is basically a wrapper around a script.
If you have the Powershell@2 task, that takes several options, that abstracts away some
parameters that you would normally have to write. A common one that you can find there
is &lsquo;workingDirectory&rsquo;, which specifies where the script will be executed. It also allows
you to either perform an inline script (where the input then is a variable for the task)
or to reference an external script.</p>
<p>A script does not have that wrapper around it, so you can customize it better and do
whatever you need to do, but you need to do everything yourself. I find myself using a
combination of both, but for AzureKeyVault it is very convienent to use the task.</p>
<h5 id="what-are-the-digit-s-in-tasks">What are the @digit &rsquo;s in tasks?</h5>
<p>The @0, @1, @2, etc are the different versions of the task, this way you can evolve your
task over time with new options, but people can still use the defined version to keep up
with that.</p>
<h2 id="practical-examples-within-the-pipeline">Practical examples within the pipeline</h2>
<h3 id="logical-operators">Logical operators</h3>
<p>Logical operators are often found in languages, They allow you to follow english like
decisions &lsquo;if something is true, then do action A, else if something else is true, do
action B, and if nothing is true, well then do action Z&rsquo;</p>
<p>In most languages that is quite simple:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> value <span style="color:#f92672">in</span> string:
</span></span></code></pre></div><p>For Azure I find it a bit harder, you have operators to &lsquo;wrap&rsquo; a condition in.
Like &rsquo;eq&rsquo;, &rsquo;ne&rsquo;, &lsquo;contains&rsquo;, &lsquo;containsValue&rsquo;, &lsquo;and&rsquo;, &lsquo;or&rsquo;, and many more.</p>
<p>They are used like this, where both perform similar validations. In this
example both checks need to be true. The first check, is parameters.key
equal to yourValue, and the second check does &lsquo;key2&rsquo; contain the string &lsquo;yourOtherValue&rsquo;.
If so, the function is true and will perform what you combine it with.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">and(eq(parameters.key, &#39;yourValue&#39;), contains(parameters[&#39;key2&#39;], &#39;yourOtherValue&#39;))</span>
</span></span></code></pre></div><p>I find this a bit harder to read especially when the and/or operators take a few additional
arguments that you want to test against. It can become unreadable quite quickly, so write out
the thing you want to do first, so that you have it &lsquo;drawn&rsquo; or &lsquo;designed&rsquo;.</p>
<p>Azure calls them &ldquo;Functions&rdquo;.</p>
<h4 id="if-then-else-example">If then else example</h4>
<p>Within Azure you can also do an if then else ofcourse. But you need to be aware of the indentation.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">job</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">task</span>: <span style="color:#ae81ff">AzureKeyvault@0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">parameters</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ae81ff">${{ if eq([&#39;parameters.test&#39;], &#39;true&#39;) }}:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">vault</span>: <span style="color:#ae81ff">test</span>
</span></span><span style="display:flex;"><span>      <span style="color:#ae81ff">${{ else }}:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">vault</span>: <span style="color:#ae81ff">something else</span>
</span></span></code></pre></div><h4 id="if-value-contains">If value contains</h4>
<p>Likewise using the if / else construct, you can also see whether a part of a text is included in a
parameter. This can be useful if you have a naming conventation or unique indicator that you can use
to define certain parameters.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">job</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">task</span>: <span style="color:#ae81ff">AzureKeyvault@0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">parameters</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#ae81ff">${{ if contains([&#39;parameters.test&#39;], &#39;your_name&#39;) }}:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">set_value</span>: <span style="color:#ae81ff">to what you need</span>
</span></span></code></pre></div><h4 id="loop-through-a-list">Loop through a list</h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">parameters</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#f92672">loop_list</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">displayName</span>: <span style="color:#ae81ff">A nice name for the loop_list</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">values</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">a</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">b</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">c</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">job</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ae81ff">${{ each value in parameters.loop_list }}:</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">task</span>: <span style="color:#ae81ff">AzureKeyvault@0</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">parameters</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">key</span>: <span style="color:#ae81ff">value</span>
</span></span></code></pre></div><h3 id="templating">Templating</h3>
<p>Ofcourse, you will probably see that the things you need to do to perform tasks is having overlapping
sections between various pipelines. If you always need to fetch your AzureKeyVault secrets with certain
custom stuff used, then you need to do that for every pipeline again and again&hellip; right?</p>
<p>Like with regular coding you can &rsquo;template&rsquo; these things. In Azure terminology a template is a file
either from the local repository, or from a remote &lsquo;resource&rsquo; repository that contains a repeatable action
that you can include in your pipeline.</p>
<p>If you work for a bigger organisation, or use sections that you need to re-use between pipelines, this
saves a lot of time and maintenance as well. A bugfix or new feature is propagated to each pipeline instantly
and effective the next time you run it. Ofcourse, on the negative side, a bug is also propagated to all
pipelines using the code.. so test it well!</p>
<h4 id="internal-resource">Internal resource</h4>
<p>With internal resource I mean specifying a resource that is within the current scope/repository like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">template</span>: <span style="color:#ae81ff">localpath/to/template.yml</span>
</span></span></code></pre></div><h4 id="external-resources">External resources</h4>
<p>One of the options to include a file is by using a &ldquo;resource&rdquo;. This goes as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">repositories</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#f92672">repository</span>: <span style="color:#ae81ff">myrepo</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">type</span>: <span style="color:#ae81ff">git</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#ae81ff">yourorg/myrepo</span>
</span></span></code></pre></div><p>Later in the code you can reference this by:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">template</span>: <span style="color:#ae81ff">path/to/template.yml@myrepo</span>
</span></span></code></pre></div><p>Where myrepo specifies the name of the repository as named at &lsquo;- repository: myrepo&rsquo;
You can ofcourse include more repositories and reference them accordingly.</p>
<h3 id="scheduling-andor-triggers">Scheduling and/or triggers</h3>
<p>Scheduling and triggers define when a pipeline gets executed if not done manually.</p>
<p>If you want to use a cron-alike way use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">schedules</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">cron</span>: <span style="color:#e6db74">&#34;59 23 * * *&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">displayName</span>: <span style="color:#ae81ff">Just before midnight</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branches</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">include</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#ae81ff">yourbranch</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">always</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>where the pipeline will run one minute before midnight using the cron definition type of a schedule.
It will only run for &lsquo;yourbranch&rsquo; in this case.</p>
<p>If you do not want to use this, either remove: schedule: and/or set:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">trigger</span>: <span style="color:#ae81ff">none</span>
</span></span></code></pre></div><p>to disable trigger based execution of your code.</p>
<h3 id="variables-and-parameters">Variables and Parameters</h3>
<p>One of the dynamic features that most CI/CD tools feature is the variables. Azure also uses them
and extends this by also using parameters.</p>
<h4 id="what-are-variables">What are variables</h4>
<p>Variables are oneline objects that specify an identifier that you can re-use throughout the entire pipeline.
They can be changed and/or exported from a task or job output so that you can use this in a different job
as well (which runs on its &lsquo;own&rsquo; agent and potentially on a very different machine, unaware of the original
job / agent).</p>
<p>For example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">variables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">my_project_name</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">value</span>: <span style="color:#e6db74">&#34;www.evilcoder.org&#34;</span>
</span></span></code></pre></div><p>Where you can use this throughout the pipeline with $(my_project_name), which will print &lsquo;<a href="https://www.evilcoder.org">www.evilcoder.org</a>&rsquo;
in this example.</p>
<p>But, the $() is common for scripting languages like &lsquo;shell&rsquo;, yet there are other ways to reference a variable
as well. This is called a runtime variable, it will be (hopefully) filled before it is ran, and the variable
is available at that moment. (task level)</p>
<p>Azure also supports so called Template expressions, which are changed at compile time of the pipeline and
cannot be changed afterwards. An example is ${{ variables.my_project_name }}, which has a different syntax
but yet it will still print &lsquo;<a href="https://www.evilcoder.org">www.evilcoder.org</a>&rsquo; in this example.</p>
<p>Another way to reference them is by using runtime expressions, which are processed at runtime, (pipeline level).
They are referred like: $[variables.my_project_name] in this case. You will see this being used when doing
comparisons or seeing whether a variable contains a certain string.</p>
<h4 id="what-are-parameters">What are parameters</h4>
<p>Parameters are more highly sophisticated variables in a nutshell. You can specify the type of the object,
provide values for them (if the list is long enough you get a dropdown), etc. What I notice when using
these parameters, when you run a pipeline manually, you get a popup asking about the details of the parameter
and unless you have a default already, you need to specify them or select them from the menu.</p>
<p>They are converted just before the pipeline runs and thus are static. If you need more dynamical items
then variables are much more flexible. However, I prefer the use of parameters to have a bigger amount of
control over the type.</p>
<p>An example parameter:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">parameters</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Webname</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">displayName</span>: <span style="color:#ae81ff">Hostname of the webservice</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">default</span>: <span style="color:#e6db74">&#34;www.evilcoder.org&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">values</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;https://www.remkolodder.nl&#34;</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;https://www.evilcoder.org&#34;</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;https://www.elvandar.org&#34;</span>
</span></span></code></pre></div><p>This will give you a selectable for the Webname parameter, which you can use later on in a task like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">script</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  hugo --baseURL ${{ parameters.Webname }}</span>
</span></span></code></pre></div><p>which will put the baseurl for a hugo site to the site specified. Note that I used the longer version
of the argument for hugo to keep it readable.</p>
<h4 id="difference-between-variables-and-parameters">Difference between variables and parameters</h4>
<p>As mentioned in the above text, a variable can be changed on the fly when needed, or exported in a job
to be re-used in a subsequent job. Parameters are more static and only converted just before runtime
of the pipeline. That makes then immutable during the run, but you have higher grip on what kind of
parameter you expect and even limit the amount of input it can handle (see the values above).</p>
<h3 id="simultaneous-execution-of-something">Simultaneous execution of something</h3>
<p>You can run several items in parallel, for that you need a strategy called: &ldquo;matrix&rdquo;.</p>
<p>What this does is it uses the keys in the matrix (the first item that you select on
the same indent), and loop through them with the key/values in it that you can use
in your task or script.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">parameters</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">matrix_you_will_be_using</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">type</span>: <span style="color:#ae81ff">object</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">default</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name_1</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">variable_a</span>: <span style="color:#e6db74">&#34;stringA&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">variable_b</span>: <span style="color:#e6db74">&#34;stringB&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name_2</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">variable_a</span>: <span style="color:#e6db74">&#34;stringAA&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">variable_b</span>: <span style="color:#e6db74">&#34;stringBB&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#ae81ff">...]</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">job</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">strategy</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">matrix</span>: <span style="color:#ae81ff">${{ parameters.matrix_you_will_be_using }}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">script</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        echo $(variable_a)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        echo $(variable_b)</span>
</span></span></code></pre></div><p>Where it will do the loop two times, one for name_1 and one for name_2, and print the variables: stringA and stringB
for the name_1 print, and stringAA and stringBB for the name_2 print. This can run concurrently, so that if you need
to do a certain test a lot of times using the same input variables but with different logic included, you can do that
en-masse. You can limit the amount of concurrent running items with : &lsquo;maxParallel&rsquo;.</p>
<p>The yaml structure defined above, starts each iteration with the name_ keys, and then you can specify them with just
their short name, if you compare that by running a loop through the object with &ldquo;each&rdquo; for example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#ae81ff">${{ each ShortName in parameters.matrix_you_will_be_using }}:</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">script</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    echo ${{ ShortName.variable_a }}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    echo ${{ ShortName.variable_b }}</span>
</span></span></code></pre></div><p>which looks much different then the stategy above.</p>
<h3 id="artifacts">Artifacts</h3>
<p>As you can see in my example image on top, there are a couple of Artifact stores mentioned, and some airgap
lines. These airgap lines suggest that you cannot access the data from one stage to another, and with the
difference in extern and internal agents, you could be limited by internal policies as well. Imagine that
you cannot fetch anything from the internet via the Internal Agents (IA), then you always need to fetch
a resource from the External Agents (EA). You could see the difference between them as airgap.</p>
<p>If you allow your Agents to access the Azure Cloud though, then you can use the Azure Artifact resource
as airgap proxy. You can see that as a huge store where you can upload &lsquo;artifacts&rsquo;, or as I reference them
as &lsquo;zip&rsquo; files, where you upload a resource from the EA, and download them ater on the IA. In this
example it could be used to create a pre-actions report, upload that via Azure Artifacts and download it in
the next stage on the IA to do something with that pre-actions report.</p>
<h4 id="artifact-types">Artifact Types</h4>
<p>There are two types of artifacts, that I am currently familiar with, those are the Pipeline Artifacts
and the Azure Artifacts,</p>
<h5 id="pipeline-artifact">Pipeline artifact</h5>
<p>This type of artifact only survives a pipeline lifetime. They are not billed either, and artifacts generated
during the run will be there until the pipeline results are deleted. This means that if your retention period
for pipelines are 10 days, then after 10 days the artifacts included in that pipeline is removed as well.</p>
<p>The usage is quite simple, An important variable within Azure DevOps is the: $(Build.ArtifactStagingDirectory) variable.
This variable marks the staging directory where &lsquo;objects&rsquo; are stored temporary before being placed in a artifact or
whatever you are going to do with it. Normally relevant files are copied there using a &lsquo;CopyFiles&rsquo; task and then
put into an artifact like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">task</span>: <span style="color:#ae81ff">PublishPipelineArtifact@1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">displayName</span>: <span style="color:#e6db74">&#39;Publish&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">targetPath</span>: <span style="color:#ae81ff">$(Build.ArtifactStagingDirectory)/webroot/**</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">${{ if eq(variables[&#39;Build.SourceBranchName&#39;], &#39;main&#39;) }}:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">artifactName</span>: <span style="color:#e6db74">&#39;webroot-prod&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">${{ else }}:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">artifactName</span>: <span style="color:#e6db74">&#39;webroot-test&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">artifactType</span>: <span style="color:#e6db74">&#39;pipeline&#39;</span>
</span></span></code></pre></div><p>Afterwards in subsequent stages or jobs, you can download this webroot-prod or webroot-test artifact (which is indeed the name of the
artifact)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">task</span>: <span style="color:#ae81ff">DownloadPipelineArtifact@2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">artifactName</span>: <span style="color:#e6db74">&#39;webroot-test&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">targetPath</span>: <span style="color:#ae81ff">$(Build.SourcesDirectory)/webroot</span>
</span></span></code></pre></div><p>which will fetch the latest version of the webroot(-prod | -test) artifact inside the pipeline. You can download resources from other
branches or pipelines as well, see the reference at <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/download-pipeline-artifact-v2?view=azure-pipelines">Microsoft</a></p>
<h4 id="azure-artifacts">Azure Artifacts</h4>
<p>I use these kind of artifacts a lot, they are billed though so you should consider your storage options and funding before using
this.</p>
<p>Azure Artifacts use incrementing numbers to identify version numbers. You can mention a specific version id yourself ofcourse,
which could be handy if you &lsquo;git tag&rsquo; your code as well to point to certain release, and create an artifact out of that with the
same version. Unlike the &lsquo;PipelineArtifact&rsquo; tasks, that well quite well describe the task at hand, Azure Artifacts use the
UniversalPackages task. By default the Artifactstagingdirectory is used to upload all files under it to the Azure Artifact
store. Note well, if you use the GUI to generate the task for you, you can select the feed etc. This will be converted into
it&rsquo;s UUID if transformed into YAML. That might be quite hard to read, so I suggest converting them to your actual feedname
and packagenames instead.</p>
<p>An example on how to create an Azure Artifact with your own tags (Assume this version is in the $(git-tag-version) variable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">task</span>: <span style="color:#ae81ff">UniversalPackages@0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">displayName</span>: <span style="color:#ae81ff">Create latest webroot artifact</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">command</span>: <span style="color:#ae81ff">publish</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">publishDirectory</span>: <span style="color:#e6db74">&#39;$(Build.ArtifactStagingDirectory)&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">vstsFeedPublish</span>: <span style="color:#e6db74">&#39;Yourproject/your-generated-feed&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">vstsFeedPackagePublish</span>: <span style="color:#e6db74">&#39;webroot&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">versionOption</span>: <span style="color:#ae81ff">custom</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">versionPublish</span>: <span style="color:#e6db74">&#39;$(git-tag-version)&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">packagePublishDescription</span>: <span style="color:#e6db74">&#39;Webroot for website&#39;</span>
</span></span></code></pre></div><p>and later on you can download that in a different job and/or stage (make sure you depend on this if you need to have the
latest version generated inside your pipeline, else you might end up downloading a different version, or the version you
want to have cannot be found. To download the latest version specify &lsquo;*&rsquo;. Else specify the exact version number.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">task</span>: <span style="color:#ae81ff">UniversalPackages@0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">displayName</span>: <span style="color:#e6db74">&#39;Download latest webroot version&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">inputs</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">command</span>: <span style="color:#ae81ff">download</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">vstsFeed</span>: <span style="color:#e6db74">&#39;Yourproject/your-generated-feed&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">vstsFeedPackage</span>: <span style="color:#e6db74">&#39;webroot&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">vstsPackageVersion</span>: <span style="color:#e6db74">&#39;$(git-tag-version)&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">downloadDirectory</span>: <span style="color:#e6db74">&#39;$(Build.SourcesDirectory)\webroot&#39;</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>Using 1password vault to access third party site(s) script-wise</title><link>/posts/2022-11-11-1password-dockerhub-tags/</link><pubDate>Fri, 11 Nov 2022 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2022-11-11-1password-dockerhub-tags/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Working at a financial institute ofcourse requires the use of using secure access to usernames and passwords and the like. For some period that had been out of reach for home consumers or smaller companies, but now with Hashicorp Vault, Ansible Vault, or smart access to applications like 1Password gives the opportunity to use these kind of smart access yourself.&lt;/p&gt;
&lt;h2 id="scope"&gt;Scope&lt;/h2&gt;
&lt;p&gt;This specific blog post, will focus on how I am using 1Password to access external applications, using their integration. If you use 1Password, you should have already setup the integration yourself.
See &lt;a href="https://developer.1password.com/docs/connect/get-started/"&gt;this link&lt;/a&gt; to get started with setting up your own integration to one of your vaults.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Working at a financial institute ofcourse requires the use of using secure access to usernames and passwords and the like. For some period that had been out of reach for home consumers or smaller companies, but now with Hashicorp Vault, Ansible Vault, or smart access to applications like 1Password gives the opportunity to use these kind of smart access yourself.</p>
<h2 id="scope">Scope</h2>
<p>This specific blog post, will focus on how I am using 1Password to access external applications, using their integration. If you use 1Password, you should have already setup the integration yourself.
See <a href="https://developer.1password.com/docs/connect/get-started/">this link</a> to get started with setting up your own integration to one of your vaults.</p>
<p>I will however describe what I did -after setting up the above- to integrate that, and I will demonstrate that with an example script.</p>
<h2 id="definitions--explanations">Definitions / explanations</h2>
<h3 id="1password">1Password</h3>
<p>To recap, if you are not familiar, 1Password started as a standalone application, that locally stored your usernames and passwords, and grew various options to automatically submit your credentials to a site, after authenticating to the app, etc. You can use it alone, but also share it with family members for a shy amount per month, or even company wide in case you are interested. Recently it grew a cloud only vault, that give the opportunity to centrally store application credentials and can be fetched from everywhere.</p>
<p>That does suggest a potential larger attack vector, then having a vault locally, but you cannot access
the data otherwise. You should make the tradeoff for yourself.</p>
<h3 id="keyvaults">Keyvaults</h3>
<p>The concept behind keyvaults, is that is generally a &lsquo;store secret data&rsquo; in a safe (aka vault). In some cases you can put something in and cannot (easily) get the information back. That is, if there are fine grained access controls in place that you can can prevent regular users from &lsquo;fetching&rsquo; data from the vault, but only have automated access to that same vault. This makes it needless to store usernames and passwords in env files or in the code itself, you can retrieve it when needed and it will get logged etc.</p>
<p>A keyvault, nowadays, can also be queried by an API, that works for the cloud providers, and also 1Password has that option. You define a certain access method for your code and/or intermediate applications (like the 1Password-Connect service), and you can programatically query an endpoint that could result in a username, password, url, and what whatever you configure within the entry in the vault.</p>
<h2 id="the-example">The example</h2>
<p>The example below, is what I use daily to check whether the php group has a new release and released a new docker image so that I can rebuild my own customized web containers. I used to just fetch the tags and parsed them, but with the requirement to use api-v2 you also need authentication, and this script provides that easily with the help of 1Password.</p>
<p>The example uses the 1password connect sdk, a toolkit that talks to the 1Password Connect application that I run in a docker container.</p>
<p>So, here comes the script, together with 1Password integration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests<span style="color:#f92672">,</span> json<span style="color:#f92672">,</span> os<span style="color:#f92672">,</span> sys
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> dotenv <span style="color:#f92672">import</span> load_dotenv
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>load_dotenv()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">###### CONFIGURATION ITEM ##########</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Password module, either 1password or file</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Note that the .env file is required in both</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># cases, but in one case we use it to connect to</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># the 1password upstream, else we use the direct</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># configuration from the file.</span>
</span></span><span style="display:flex;"><span>password_module <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;1password&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> password_module <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;1password&#34;</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Import modules required for 1password</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">import</span> onepasswordconnectsdk
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">from</span> onepasswordconnectsdk.client <span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>        Client,
</span></span><span style="display:flex;"><span>        new_client_from_environment,
</span></span><span style="display:flex;"><span>        new_client
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Start a new 1password controller from environment</span>
</span></span><span style="display:flex;"><span>    client: Client <span style="color:#f92672">=</span> new_client_from_environment()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Setup the connection with the previous client section.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># We look for a specific &#39;name of your item&#39; in your application vault.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># You can ofcourse set that in a variable or use it as parameter/cli param.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    config <span style="color:#f92672">=</span> onepasswordconnectsdk<span style="color:#f92672">.</span>load_dict(client, {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;username&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;opitem&#34;</span>: <span style="color:#e6db74">&#34;name of your item&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;opfield&#34;</span>: <span style="color:#e6db74">&#34;.username&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;password&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;opitem&#34;</span>: <span style="color:#e6db74">&#34;name of your item&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;opfield&#34;</span>: <span style="color:#e6db74">&#34;.password&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;url&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;opitem&#34;</span>: <span style="color:#e6db74">&#34;name of your item&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;opfield&#34;</span>: <span style="color:#e6db74">&#34;sitedata.url&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Items fetched from 1password record and assigned to variable</span>
</span></span><span style="display:flex;"><span>    base_url <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;url&#34;</span>]
</span></span><span style="display:flex;"><span>    username <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;username&#34;</span>]
</span></span><span style="display:flex;"><span>    password <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;password&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># End of 1Password information</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">elif</span> password_module <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;file&#34;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Items configured in .env file</span>
</span></span><span style="display:flex;"><span>    base_url <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#39;BASE_URL&#39;</span>)
</span></span><span style="display:flex;"><span>    username <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#39;USERNAME&#39;</span>)
</span></span><span style="display:flex;"><span>    password <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#39;PASSWORD&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    print(<span style="color:#e6db74">&#34;You need to configure a valid backend, we cannot proceed like this!</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>    sys<span style="color:#f92672">.</span>exit(<span style="color:#e6db74">&#34;Please configure the right settings!&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>image <span style="color:#f92672">=</span> sys<span style="color:#f92672">.</span>argv[<span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>version <span style="color:#f92672">=</span> sys<span style="color:#f92672">.</span>argv[<span style="color:#ae81ff">2</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Set image login and tags url</span>
</span></span><span style="display:flex;"><span>login_url <span style="color:#f92672">=</span> base_url <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/v2/users/login&#39;</span>
</span></span><span style="display:flex;"><span>tags_url <span style="color:#f92672">=</span> base_url <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/v2/repositories/library/&#39;</span> <span style="color:#f92672">+</span> image <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/tags/?page_size=10000&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">###### END CONFIGURATION ITEM ##########</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> requests<span style="color:#f92672">.</span>Session() <span style="color:#66d9ef">as</span> session:
</span></span><span style="display:flex;"><span>    post <span style="color:#f92672">=</span> session<span style="color:#f92672">.</span>post(login_url, json<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;username&#34;</span>: username, <span style="color:#e6db74">&#34;password&#34;</span>: password})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Variable setting</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Set token to token.</span>
</span></span><span style="display:flex;"><span>    token <span style="color:#f92672">=</span> post<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">&#39;token&#39;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Add the authentication token to the headers.</span>
</span></span><span style="display:flex;"><span>    headers[<span style="color:#e6db74">&#39;Authorization&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;JWT </span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span><span style="color:#f92672">.</span>format(token)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># create the tags list and fetch the proper url</span>
</span></span><span style="display:flex;"><span>    tags_list <span style="color:#f92672">=</span> session<span style="color:#f92672">.</span>get(tags_url, headers<span style="color:#f92672">=</span>headers)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># refactor the response under tags_list as json and store it in json_response</span>
</span></span><span style="display:flex;"><span>    json_response <span style="color:#f92672">=</span> tags_list<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># We only need the names in this purpose, loop through the results and match the</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># version we specified on the commmandline and then print it if there is a match.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> name <span style="color:#f92672">in</span> json_response[<span style="color:#e6db74">&#39;results&#39;</span>]:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> version <span style="color:#f92672">in</span> name[<span style="color:#e6db74">&#34;name&#34;</span>]:
</span></span><span style="display:flex;"><span>            print(name[<span style="color:#e6db74">&#34;name&#34;</span>])
</span></span></code></pre></div>]]></content:encoded></item><item><title>WorkING</title><link>/posts/2022-05-17-working/</link><pubDate>Sun, 25 Sep 2022 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2022-05-17-working/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I started writing this blog post back in May, where I (a bit late) wanted to tell where
I went after working for Snow/Sue for 15 years. But I got distracted by other things,
amongst them a lot of cool things at work.. so perhaps I should finally finish the entry.&lt;/p&gt;
&lt;p&gt;A year ago (wow, time flies!) I rejoined ING. I was looking for a job where I could continue
my Coaching experience and continue to learn, and of course using my strong technical background
where possible.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>I started writing this blog post back in May, where I (a bit late) wanted to tell where
I went after working for Snow/Sue for 15 years. But I got distracted by other things,
amongst them a lot of cool things at work.. so perhaps I should finally finish the entry.</p>
<p>A year ago (wow, time flies!) I rejoined ING. I was looking for a job where I could continue
my Coaching experience and continue to learn, and of course using my strong technical background
where possible.</p>
<p>During the COVID period, the occasional band &ldquo;The Streamers&rdquo;, was on TV, together with a load of
ING advertisements. ING sponsored the event and that triggered me to have a look at their jobs.</p>
<p>The role that I found there, interested me a lot, and I decided to just give it a try.
It was one of the best decisions of &lsquo;2021&rsquo; :-).</p>
<h3 id="interview">Interview</h3>
<p>Some old friends still work at ING, so I asked one of them (Hi Marc), what the role was about and
what I could expect. Since it involved a lot of coaching and using my technical background (in a bit
of a different area then normally), I asked the recruiters to do an interview.</p>
<p>I had an introduction meeting with one of the recruiters, and then with Ad and Edith, and finally with
my manager Jos. All conversations where fun conversations and quickly after each other. I got an offer
and decided to sign for the offer. We together decided that I would start at Sept 1st 2021.</p>
<h3 id="starting">Starting</h3>
<p>So, starting in Covid as Chapter Lead, for a company that I still knew, but changed heavily.. that&rsquo;s kinda.. interesting.
My first day at the office I met Jos in Amsterdam, and together we got my Laptop and he showed me how to set it up and where
to find things. I decided to make appointments with everyone from my team and slowly introduce myself in those meetings,
how else can you do that when nobody is allowed to go to the office?</p>
<p>I think that was a good call, I quickly made contact with everyone, even with the people not in my direct team and got
to learn them a bit. Ofcourse there was more work to do there from my angle, but a start was made. Being a Chapter Lead
means HR related activities within ING, so a lot of contact with the people, but also it is a combination role with an
engineering part. I did some things with Virtualisation and Storage in the past, but not that heavily as all my fellow
technical engineers are doing. Quite a task for me to get up to speed with them and how do I respond to the question:
What are you going to focus on and bring to the team Remko? I needed to find a way for quite some time, next to learning
everyone and learning ING.</p>
<h4 id="management">Management</h4>
<p>As mentioned I am the Chapter Lead, which means I have HR responsibilities, amongst them helping my team members with their
individual development plans, help setting their goals, adressing and resolving issues they come across with, time-off,
salaries, health (up to my allowed involvement level).  I also have periodic one-on-one conversations with each member of
the team. I try to be an open and reachable manager, honest and fair. If I can help, one should let me know, but if I need
to make a harder decision, I will always to that. After all, that is part of my job.</p>
<p>Together with a fellow Chapter Lead and the Product Owner, we maintain the virtualisation and storage infrastructure for ING,
divided in three teams.</p>
<h4 id="technical">Technical</h4>
<p>Next to the managerial part, I also have a technical role. I am an active member of the virtualisation team, where we
maintain and manage the Private Cloud Infrastructure for ING. As mentioned I needed to find a way on how to contribute to the
team the best as I can, and I think I found a way there with the automation using Ansible. At the moment of writing I am the
main person developing (admittedly on the work done by Liam and Leon) the automation roll out further and making it stable.
I am also one of the persons in the team that create our Azure DevOps Pipelines, building the CI/CD street for these
automations.</p>
<p>Using the Agile approach we do that in a 2 week sprint cycle, and everytime we get more into a stable situation.</p>
<p>One of the things that I really like is that we share our knowledge when we can and offer demo&rsquo;s etc to our team but also
to the rest of the Tribe or wider if needed. Everyone is always willing to help and that makes it the perfect allround team
for me.</p>
<h2 id="your-amibition">Your amibition?</h2>
<p>My personal amibition is to grow more as Coach, but also get to learn the ropes of the Virtualisation
paltform more and more. I am adding value to the team already, but I have a strong drive to do more
or be able to do more (if time permits :-)).</p>
<p>If you have Questions or want to be WorkING at ING as well, let me know and I will see what I can do for you!</p>
]]></content:encoded></item><item><title>Waiving goodbye to my Tesla Model3</title><link>/posts/2021-12-20-tesla-model3/</link><pubDate>Mon, 20 Dec 2021 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2021-12-20-tesla-model3/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;During my time at Snow or later Sue, I was allowed to drive various types of cars. I mainly drove
Volkswagen&amp;rsquo;s, from the Golf to the Golf Variant, but also an Audi, a Skoda, a Prius (jikes) and in
the end I was allowed to drive a Tesla Model3.&lt;/p&gt;
&lt;p&gt;As a computernerd that is an awesome thing to drive, it has all kind of connectivity options, you can
monitoy it from remote with applications like teslamate and store essential data from it.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>During my time at Snow or later Sue, I was allowed to drive various types of cars. I mainly drove
Volkswagen&rsquo;s, from the Golf to the Golf Variant, but also an Audi, a Skoda, a Prius (jikes) and in
the end I was allowed to drive a Tesla Model3.</p>
<p>As a computernerd that is an awesome thing to drive, it has all kind of connectivity options, you can
monitoy it from remote with applications like teslamate and store essential data from it.</p>
<h2 id="and-now">And now?</h2>
<p>After saying goodbye to Sue, I was ofcourse also returning my car, then called Mars as Bram  (one of my kids)
named him. After so many moons of driving a car, and almost a year with an all electric vehicle that was something
to learn to cope with. But so far I am managing fine, I dont owe a car at all and where needed I can borrow one
most of the time. We bike a lot more nowadays and that works out fine. My new employer has it&rsquo;s buildings easily
accessible by public transport, so nothing to worry there as well.</p>
<h2 id="map">Map</h2>
<p>So, driving such a car, in my then role as Technical Field Manager, I drove, or should have driven, through the
entire country. Since Covid, that never happened as before Covid. Still I drove around the country quite a bit
and luckily teslamate drawed me a map. The first period isn&rsquo;t in there, I didnt have the application back then
but for approximately a year, you can see my driving in the country.</p>
<p><img loading="lazy" src="/images/dutch-map-tesla.png" type="" alt="Dutch-map-driving"  /></p>
]]></content:encoded></item><item><title>Fetch Flexweb images for backup purposes in Python</title><link>/posts/2021-10-17-flexweb-image-backup-script/</link><pubDate>Sun, 17 Oct 2021 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2021-10-17-flexweb-image-backup-script/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Since some time our kids to to daycare, and the occassionaly place photos on the secured environment.
It&amp;rsquo;s a bit problematic to download the photos. On the phone it feels a bit weird how that works and online you can
only fetch one photo at at time. But since they are there for a longer period already, that would mean manually
download a lot of photos.&lt;/p&gt;
&lt;p&gt;So, I decided to write a little python wrapper. Using proxyman in between I analyzed the requests and contents and
was able to determine how the login procedure works. We first POST a request to the LOGIN URL and bind a session to
the &amp;lsquo;requests&amp;rsquo; structure , which we will use later to POST to the album URL (you need to do that first, it seems that
the PHPSESSID is then placed on the allowed list or something), which will result in a list of available photos. We can
then fetch the items by iterating over the dict and nested list and download the images. We do our best to not overload
the server so we delay every other request like a regular browser would also do.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Since some time our kids to to daycare, and the occassionaly place photos on the secured environment.
It&rsquo;s a bit problematic to download the photos. On the phone it feels a bit weird how that works and online you can
only fetch one photo at at time. But since they are there for a longer period already, that would mean manually
download a lot of photos.</p>
<p>So, I decided to write a little python wrapper. Using proxyman in between I analyzed the requests and contents and
was able to determine how the login procedure works. We first POST a request to the LOGIN URL and bind a session to
the &lsquo;requests&rsquo; structure , which we will use later to POST to the album URL (you need to do that first, it seems that
the PHPSESSID is then placed on the allowed list or something), which will result in a list of available photos. We can
then fetch the items by iterating over the dict and nested list and download the images. We do our best to not overload
the server so we delay every other request like a regular browser would also do.</p>
<p>If you are not on the session allow list or do not have the Cookie, then you will get images of &lsquo;378&rsquo; bytes big, which
looks like an unallowed image. From limited testing you are not able to download photos which is not in your list and/or
belong to you. It is not a guarantee though!</p>
<p>Ofcourse there are probably easier or better methods. Please let me know in that case, so that I can learn from it ;-)</p>
<p>Note that you need the loadenv plugin to get variables from the environment. They are recorded as &ldquo;VARIABLE=VALUE&rdquo;
items and work like BASH.</p>
<h3 id="the-python-code-used">The Python Code used</h3>
<p>below is the code that I used to obtain the currently available photos. Adopt as you need it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests<span style="color:#f92672">,</span> json<span style="color:#f92672">,</span> time<span style="color:#f92672">,</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> dotenv <span style="color:#f92672">import</span> load_dotenv
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>load_dotenv()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># URL Contructs</span>
</span></span><span style="display:flex;"><span>base_url <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#39;BASE_URL&#39;</span>)
</span></span><span style="display:flex;"><span>image_url <span style="color:#f92672">=</span> base_url <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/ouder/media/download/media/&#39;</span>
</span></span><span style="display:flex;"><span>login_url <span style="color:#f92672">=</span> base_url <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/login/login&#39;</span>
</span></span><span style="display:flex;"><span>album_url <span style="color:#f92672">=</span> base_url <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/ouder/fotoalbum/standaardalbum&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># private settings from environment file</span>
</span></span><span style="display:flex;"><span>username <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#39;USERNAME&#39;</span>)
</span></span><span style="display:flex;"><span>password <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#39;PASSWORD&#39;</span>)
</span></span><span style="display:flex;"><span>photo_path <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>getenv(<span style="color:#e6db74">&#39;PHOTO_PATH&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Range between which period we should search for photos</span>
</span></span><span style="display:flex;"><span>year_start <span style="color:#f92672">=</span> yyyy
</span></span><span style="display:flex;"><span>month_start <span style="color:#f92672">=</span> mm
</span></span><span style="display:flex;"><span>year_end <span style="color:#f92672">=</span> yyyy
</span></span><span style="display:flex;"><span>month_end <span style="color:#f92672">=</span> mm
</span></span><span style="display:flex;"><span><span style="color:#75715e"># End of range selection</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Login needs a username, password and role</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># role: 7 = login as parent(/ouder)</span>
</span></span><span style="display:flex;"><span>params <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;username&#39;</span>: username,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;password&#39;</span>: password,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;role&#39;</span>: <span style="color:#e6db74">&#39;7&#39;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Pretend we are a real browser, if there are checks for user agent, we can work around this.</span>
</span></span><span style="display:flex;"><span>headers <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;User-Agent&#39;</span>: <span style="color:#e6db74">&#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># assign the start year and month to the work variables.</span>
</span></span><span style="display:flex;"><span>year <span style="color:#f92672">=</span> year_start
</span></span><span style="display:flex;"><span>month <span style="color:#f92672">=</span> month_start
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> requests<span style="color:#f92672">.</span>Session() <span style="color:#66d9ef">as</span> session:
</span></span><span style="display:flex;"><span>    post <span style="color:#f92672">=</span> session<span style="color:#f92672">.</span>post(login_url, headers<span style="color:#f92672">=</span>headers, data<span style="color:#f92672">=</span>params)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Variable setting</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># use the PHPSESSID header and assign it the set-Cookie parameter.</span>
</span></span><span style="display:flex;"><span>    headers[<span style="color:#e6db74">&#34;PHPSESSID&#34;</span>] <span style="color:#f92672">=</span> post<span style="color:#f92672">.</span>headers[<span style="color:#e6db74">&#39;set-Cookie&#39;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Loop through year/month cycle (between range).</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Folder definition which will be reset every month and year and seen whether the folder exists. If not create it.</span>
</span></span><span style="display:flex;"><span>        folder <span style="color:#f92672">=</span> photo_path <span style="color:#f92672">+</span> str(year) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/&#39;</span> <span style="color:#f92672">+</span> str(month) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>exists(folder):
</span></span><span style="display:flex;"><span>                os<span style="color:#f92672">.</span>makedirs(folder)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># assign the year month dict, this will be used to post to the correct page where we can fetch the resulting image list.</span>
</span></span><span style="display:flex;"><span>        year_month <span style="color:#f92672">=</span> { <span style="color:#e6db74">&#34;year&#34;</span>: year, <span style="color:#e6db74">&#34;month&#34;</span>: month }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Post the year month dict, in response we will receive a list of available photoids.</span>
</span></span><span style="display:flex;"><span>        post <span style="color:#f92672">=</span> session<span style="color:#f92672">.</span>post(album_url, headers<span style="color:#f92672">=</span>headers, data<span style="color:#f92672">=</span>year_month)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># The results of the album post is a json structure with &#34;FOTOS&#34; and a list of the available photos.</span>
</span></span><span style="display:flex;"><span>        json_response <span style="color:#f92672">=</span> post<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Loop through the list in the json FOTOS structure</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> photo <span style="color:#f92672">in</span> json_response[<span style="color:#e6db74">&#34;FOTOS&#34;</span>]:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Construct the photo url</span>
</span></span><span style="display:flex;"><span>            photo_source_big <span style="color:#f92672">=</span>  image_url <span style="color:#f92672">+</span> photo
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Obtain the photo</span>
</span></span><span style="display:flex;"><span>            r <span style="color:#f92672">=</span> session<span style="color:#f92672">.</span>get(photo_source_big, headers<span style="color:#f92672">=</span>headers)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># For every result, write the file to the structure (yyyy/mm) with the obtained name.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">with</span> open(folder <span style="color:#f92672">+</span> photo <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;.jpg&#39;</span>, <span style="color:#e6db74">&#39;wb&#39;</span>) <span style="color:#66d9ef">as</span> handler:
</span></span><span style="display:flex;"><span>                handler<span style="color:#f92672">.</span>write(r<span style="color:#f92672">.</span>content)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># Sleep 10 seconds between requests to not overload the service.</span>
</span></span><span style="display:flex;"><span>                time<span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">10</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># After doing the loop if we match the end year and month, break out.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> year <span style="color:#f92672">==</span> year_end <span style="color:#f92672">and</span> month <span style="color:#f92672">==</span> month_end:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># If the month is January, decrement the year and reset the month to 12.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> month <span style="color:#f92672">==</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>            year <span style="color:#f92672">=</span> year <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>            month <span style="color:#f92672">=</span> <span style="color:#ae81ff">12</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># If the month is not 1, we can decrement it with 1.</span>
</span></span><span style="display:flex;"><span>            month <span style="color:#f92672">=</span> month <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># end</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>Leaving Snow / Sue.</title><link>/posts/2021-08-01-leaving-sue/</link><pubDate>Sun, 01 Aug 2021 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2021-08-01-leaving-sue/</guid><description>&lt;h2 id="leaving"&gt;Leaving&lt;/h2&gt;
&lt;p&gt;After 15 precious years with many ups and some downs (personally) I sadly decided to leave the mothership
of Sue and be part of another company. I will announce which company later on, I need to make it a bit
exciting to read right? ;-)&lt;/p&gt;
&lt;p&gt;Leaving after so many years is a difficult thing, or at least for me. Sue has been part for 3/4 of my
professional life, 2 out of my 3 kids where born during my time at Sue. I was seriously ill a few years
ago, which made me partially deaf, all during my time at Sue. It feels like close family!&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="leaving">Leaving</h2>
<p>After 15 precious years with many ups and some downs (personally) I sadly decided to leave the mothership
of Sue and be part of another company. I will announce which company later on, I need to make it a bit
exciting to read right? ;-)</p>
<p>Leaving after so many years is a difficult thing, or at least for me. Sue has been part for 3/4 of my
professional life, 2 out of my 3 kids where born during my time at Sue. I was seriously ill a few years
ago, which made me partially deaf, all during my time at Sue. It feels like close family!</p>
<h2 id="so-why-leave">So why leave?</h2>
<p>If you have read the above, it does not make much sense to leave right? Yeah, you are entirely correct.
In my last two years at Sue, I opted to become one of the three Technical Field Managers (TFM), which I
was chosen to do so. I enjoyed that a lot. The coaching part was part of my assignments more or less but
never on such scale. I really like that. I am still technically up to speed, I recently recertified my
CCNP. At the same time though I was also following a &ldquo;Coaching&rdquo; training, which I recently certified for
as well.</p>
<p>Sadly my role was coming to an end and Sue and I could not find a role that would fit my ambition. I found
another role in a different company that allows me to coach directly from start, and use my technical
background as well.</p>
<p>I look back with many many pleasant memories. Snow (Sue when I started working there) had always been a
good and pleasant employer and allowed me to evolve where I am now. A big thank you for the company but
even more for the people that work there, they supported me and where there when things went wrong on my
side (see above); when my kids where born etc. So with pleasant memories and a tear, I will be saying
goodbye officially at the end of August.</p>
<p>Dear people of Sue/Snow, colleague&rsquo;s, friends, thank you very much for every smile, tear, friendship, up and
down for the last 15 years. You have been great! I was lucky to work with all of you!</p>
]]></content:encoded></item><item><title>Recertified for CCNP - ENCOR</title><link>/posts/2021-06-03-ccnp-recertified/</link><pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2021-06-03-ccnp-recertified/</guid><description>&lt;h2 id="ccnp---encor"&gt;CCNP - ENCOR&lt;/h2&gt;
&lt;p&gt;&amp;lsquo;Recently&amp;rsquo; Cisco changed the CCNP track quite a lot. In the past it was composed of vertical colums so to
say with Routing, Switching and Tshoot as seperated colums. This had the advantage that you could focus on
pure Routing and pure Switching and no need to worry about bringing many different knowledge into the exam.
To recertify you needed to pass one of the colums to pass and extend the certification for another three
years.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="ccnp---encor">CCNP - ENCOR</h2>
<p>&lsquo;Recently&rsquo; Cisco changed the CCNP track quite a lot. In the past it was composed of vertical colums so to
say with Routing, Switching and Tshoot as seperated colums. This had the advantage that you could focus on
pure Routing and pure Switching and no need to worry about bringing many different knowledge into the exam.
To recertify you needed to pass one of the colums to pass and extend the certification for another three
years.</p>
<p>That is no longer the case, instead of being vertical it is now horizontal. Meaning you need to bring
a lot of broad knowledge to the table to be able to do the exam. There are many topics being passed and
I will try to write some words about the topics. In order they are:</p>
<ul>
<li>Software Defined Networks, SD-WAN, SD-Access</li>
<li>Wireless</li>
<li>LISP</li>
<li>Python</li>
<li>Routing</li>
<li>Switching</li>
</ul>
<p>But first I want to tell something about the changed world.</p>
<h2 id="changes">Changes</h2>
<p>So as said the world is changing, and it is changing rapidly. We all can see and know that. But that goes
for networks as well. When I started working (back in the dino days.. 2001) the world was rather easy.
There were a few switches and a few routers and the network was logically devided. ACL&rsquo;s where in place
to protect things and life was good. We could &ldquo;easily&rdquo; oversee things and manually update them and everyone
was mostly happy.</p>
<p>But then things changed, things like Virtual Machines started to appear, networks grew, demand grew.
Wireless networks were introduced, people could connect from everywhere, with every device. No one is willing
to connect to a cable anymore and our servers are placed everywhere and could live in Datacenter A, B, C or D.
Automation appeared, we could spin up machines and configurations when needed and automatically deploy them.
If there is need for more machines or facilities the cloud can auto scale and do magic things.  That basically
asks for connectivity that is all over. And if you move from location A to B, or a machine gets live migrated
it still needs the same connectivity and be reachable on the same addressing. This is where SDN comes in.</p>
<h2 id="software-defined-networking-sd-wan-sd-access">Software Defined Networking, SD-WAN, SD-Access</h2>
<p>The network is still build from routers and switches, but that is just a transport layer in the SDN world.
SDN provides an overlay network, which in my view is organic and grows and shrinks where needed, but moves
with devices and users. A user is no longer bound to an IP address, but instead is an object in the network
which can live everywhere. As long as the user is reachable, the same connectivity is possible. The same
goes for servers, as long as the server is reachable it doesn&rsquo;t matter where the device lives. As long as
the overlay network can find it, it works. If a wireless devices &lsquo;roams&rsquo; (moves between access points)
it doesn&rsquo;t matter if that is in building a or b, smart setups tunnel the traffic to the proper controller
and process it.</p>
<p>Cisco created SD-WAN, SD-Access and uses VXLAN with VNI and VNID extensively for this purpose. It is
a large piece of the CCNP ENCOR exam. You need to know what the previous terms are and how they are
build and communicate.</p>
<h2 id="wireless">Wireless</h2>
<p>In the previous incarnations of the CCNP exam, I never had much to do with WiFi networks or hardware like WLC&rsquo;s.
But people dont want cables anymore, they want quicker and better WiFi and get the best experience without that
annoying cable that is always to short or limited in movement. So CCNP grew WiFi topics. But as Administrator
you want it to be practical as well, so you connect it to the SD-fabric. You also need to pick the right antenna
and need to understand what kind of interference you have and what all those magic numbers mean in the statistics
and/or WLC. You need to study Wireless well!</p>
<h2 id="lisp">LISP</h2>
<p>I first assumed that this was a programming language, but is also a router locator/node locator
protocol. Since devices can roam on multiple places and cross (in legacy thinking) boundaries, the network
needs to know where to find an object. The LISP set of protocols and functions come into play to quicky find
an EID and redirect traffic to the proper place. Or use border nodes / gateways to communicate to and from
external networks.</p>
<h2 id="python">Python</h2>
<p>So who knew that a scripting or programming language (lets not start the debate on what it is!) was going to
be part of the CCNP exam? I didn&rsquo;t and there it was. You need to be able to query data from REST API&rsquo;s and
such and be able to understand how to handle them. That includes understanding JSON and how to handle it.
You can use the postman application to test api&rsquo;s and such and generate pythoncode for you, but you will need
to understand them all for the ENCOR exam.</p>
<h2 id="routing-and-switching">Routing and Switching</h2>
<p>This is the part that I first learned with CCNP, there are still topics like STP, VLAN, VTP, EtherChannel,
BGP, OSPF, EIGRP, HSRP, VRRP, GLBP, IPv6, IPv4, NAT, Multicast. Eventhough it isn&rsquo;t as focussed as the
Routing and Switching exams of the past, you still need to have solid understanding about them.</p>
<h2 id="summary">Summary</h2>
<p>As you can see, the topics grew and got broader, many new techniques are being asked and you will need
solid understanding of the above. You also need to know about some security applications like Umbrella
and Stealthwatch, but the above topics are the largest I think.</p>
<p>If you want to discuss this with me, have ideas or disagree, just email me at webmaster a.t. evilcoder.org</p>
]]></content:encoded></item><item><title>Clash of Clans Python Scripts</title><link>/posts/2021-05-14-clashofclans-python/</link><pubDate>Fri, 14 May 2021 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2021-05-14-clashofclans-python/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;For years I have been playing &amp;lsquo;Clash of Clans&amp;rsquo;. A strategy game that requires you to actively engage with others in either
direct battles, or group based battles. You can improve your village by leveling-up housing, spells, troops, the town
hall, heroes, having boosters etc. Ofcourse the makers allow you to play for free, which can take quite some time to get
progress, or you can buy additional resources and improvements that speed up the process.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>For years I have been playing &lsquo;Clash of Clans&rsquo;. A strategy game that requires you to actively engage with others in either
direct battles, or group based battles. You can improve your village by leveling-up housing, spells, troops, the town
hall, heroes, having boosters etc. Ofcourse the makers allow you to play for free, which can take quite some time to get
progress, or you can buy additional resources and improvements that speed up the process.</p>
<p>I have been playing this since I think 2014, with in between some resting periods. I think you cannot play this continuously
without being bored or frustrated that things take a long time and get more expensive (with internal resources) after every
upgrade.</p>
<p>My last round was from 2016 till just a few weeks ago. I played for the clan Holland Dukes, a nice group of people, with a good
mixture of young people and older people. Both are represented in the group as member, Elder and coLeaders.</p>
<h2 id="why-writing-additional-scripts">Why writing additional scripts?</h2>
<p>You can view your stats mainly through the game, but that is more difficult when you want to get an overview of multiple users
or see others stats. I always had a site for Holland Dukes, but it was not really filled with proper content. I planned on
working with this when I tried to understand more of gohugo (a static website generator, written in go). So in the end I did.
I needed to workout how Gohugo was going to help me with this first. If someone wants to know how I did that, let me know
how I can help and I will try to assist.</p>
<p>After having the skeleton for the site, which added Chart.JS and more stuff I needed to write several scripts that got me the
API data. I decided to write these scripts in Python since it is multi-portable and can talk to API easily. I never wrote something
like this before so it was a learning experience. I fetched the clan details because every detail needed was inside.</p>
<h3 id="clash-of-clans">Clash of Clans</h3>
<h3 id="the-api-code-python">The API code: Python</h3>
<p>Below is the code that I used to fetch the clan information from the API.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests<span style="color:#f92672">,</span> json<span style="color:#f92672">,</span> urllib<span style="color:#f92672">,</span> time
</span></span><span style="display:flex;"><span>home_token <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;home_token here&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># version used on webhost itself</span>
</span></span><span style="display:flex;"><span>token <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;production_token here&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cur_time <span style="color:#f92672">=</span> int(str(time<span style="color:#f92672">.</span>time())<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#39;.&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>))
</span></span><span style="display:flex;"><span>clantag <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;clan_tag&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>headers <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#39;Authorization&#39;</span>: <span style="color:#e6db74">&#39;Bearer &#39;</span> <span style="color:#f92672">+</span> token }
</span></span><span style="display:flex;"><span>url_clan <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://api.clashofclans.com/v1/clans/%23&#39;</span> <span style="color:#f92672">+</span> clantag[<span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>players <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>all_info <span style="color:#f92672">=</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>out_all <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;../json_data/clan-info.json&#39;</span>
</span></span><span style="display:flex;"><span>out_clan <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;../json_data/clan-info-clan.json&#39;</span>
</span></span><span style="display:flex;"><span>out_players <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;../json_data/clan-info-players.json&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(url_clan, headers<span style="color:#f92672">=</span>headers)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> r<span style="color:#f92672">.</span>ok:
</span></span><span style="display:flex;"><span>   json_clan <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(r<span style="color:#f92672">.</span>text)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>updated_line <span style="color:#f92672">=</span> { <span style="color:#e6db74">&#34;updatedOn&#34;</span>: cur_time }
</span></span><span style="display:flex;"><span>clan_line <span style="color:#f92672">=</span> { <span style="color:#e6db74">&#34;clan&#34;</span>: json_clan }
</span></span><span style="display:flex;"><span>all_info <span style="color:#f92672">=</span> updated_line
</span></span><span style="display:flex;"><span>all_info<span style="color:#f92672">.</span>update(clan_line)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> player <span style="color:#f92672">in</span> json_clan[<span style="color:#e6db74">&#39;memberList&#39;</span>]:
</span></span><span style="display:flex;"><span>    player_tag <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;tag&#39;</span>]
</span></span><span style="display:flex;"><span>    url_player <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://api.clashofclans.com/v1/players/%23&#39;</span> <span style="color:#f92672">+</span> player_tag[<span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>    rp <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(url_player, headers<span style="color:#f92672">=</span>headers)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> rp<span style="color:#f92672">.</span>ok:
</span></span><span style="display:flex;"><span>       json_player <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(rp<span style="color:#f92672">.</span>text)
</span></span><span style="display:flex;"><span>       players<span style="color:#f92672">.</span>append(json_player)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>player_line <span style="color:#f92672">=</span> { <span style="color:#e6db74">&#34;players&#34;</span>: players }
</span></span><span style="display:flex;"><span>all_info<span style="color:#f92672">.</span>update(player_line)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(out_all, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> fw_all:
</span></span><span style="display:flex;"><span>    fw_all<span style="color:#f92672">.</span>write(json<span style="color:#f92672">.</span>dumps(all_info))
</span></span><span style="display:flex;"><span>    fw_all<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(out_clan, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> fw_clan:
</span></span><span style="display:flex;"><span>    fw_clan<span style="color:#f92672">.</span>write(json<span style="color:#f92672">.</span>dumps(json_clan))
</span></span><span style="display:flex;"><span>    fw_clan<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(out_players, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> fw_players:
</span></span><span style="display:flex;"><span>    fw_players<span style="color:#f92672">.</span>write(json<span style="color:#f92672">.</span>dumps(players))
</span></span><span style="display:flex;"><span>    fw_players<span style="color:#f92672">.</span>close()
</span></span></code></pre></div><h3 id="parsing-of-the-fetched-data">Parsing of the fetched data</h3>
<p>Next to fetching the data with the API, I needed to parse the returned
JSON and do something with it. I write the name_ variables to a gohugo
markdown file where it is being used in the templates to generate a result.</p>
<p>For gohugo to understand these examples I needed to escape the curly brackets
later on.</p>
<p>Sure enough I could not use variables instead and print them directly, which would
make the code a lot smaller, but from my POV that would reduce readability.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>json_file_all <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;json_data/clan-info.json&#39;</span>
</span></span><span style="display:flex;"><span>json_file <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;../json_data/clan-info-players.json&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(json_file) <span style="color:#66d9ef">as</span> f:
</span></span><span style="display:flex;"><span>     data <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>load(f)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> player <span style="color:#f92672">in</span> data:
</span></span><span style="display:flex;"><span>    name <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;name&#39;</span>]
</span></span><span style="display:flex;"><span>    name_tag <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;tag&#39;</span>]
</span></span><span style="display:flex;"><span>    name_explevel <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;expLevel&#39;</span>]
</span></span><span style="display:flex;"><span>    name_thlevel <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;townHallLevel&#39;</span>]
</span></span><span style="display:flex;"><span>    name_trophies <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;trophies&#39;</span>]
</span></span><span style="display:flex;"><span>    name_besttrophies <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;bestTrophies&#39;</span>]
</span></span><span style="display:flex;"><span>    name_warstars <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;warStars&#39;</span>]
</span></span><span style="display:flex;"><span>    name_attackwins <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;attackWins&#39;</span>]
</span></span><span style="display:flex;"><span>    name_defensewins <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;defenseWins&#39;</span>]
</span></span><span style="display:flex;"><span>    name_bhlevel <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;builderHallLevel&#39;</span>]
</span></span><span style="display:flex;"><span>    name_versustrophies <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;versusTrophies&#39;</span>]
</span></span><span style="display:flex;"><span>    name_bestversustrophies <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;bestVersusTrophies&#39;</span>]
</span></span><span style="display:flex;"><span>    name_versusbattlewins <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;versusBattleWins&#39;</span>]
</span></span><span style="display:flex;"><span>    name_versusbattlewincount <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;versusBattleWinCount&#39;</span>]
</span></span><span style="display:flex;"><span>    name_role <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;role&#39;</span>]
</span></span><span style="display:flex;"><span>    name_donations <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;donations&#39;</span>]
</span></span><span style="display:flex;"><span>    name_donationsreceived <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;donationsReceived&#39;</span>]
</span></span><span style="display:flex;"><span>    name_clan <span style="color:#f92672">=</span> dict(player[<span style="color:#e6db74">&#39;clan&#39;</span>])
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#39;townHallWeaponLevel&#39;</span> <span style="color:#f92672">in</span> player:
</span></span><span style="display:flex;"><span>        name_townhallweaponlevel <span style="color:#f92672">=</span> player[<span style="color:#e6db74">&#39;townHallWeaponLevel&#39;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#39;league&#39;</span> <span style="color:#f92672">in</span> player:
</span></span><span style="display:flex;"><span>        name_league <span style="color:#f92672">=</span> dict(player[<span style="color:#e6db74">&#39;league&#39;</span>])
</span></span><span style="display:flex;"><span>    name_achievements <span style="color:#f92672">=</span> list(player[<span style="color:#e6db74">&#39;achievements&#39;</span>])
</span></span><span style="display:flex;"><span>    name_troops <span style="color:#f92672">=</span> list(player[<span style="color:#e6db74">&#39;troops&#39;</span>])
</span></span><span style="display:flex;"><span>    name_heroes <span style="color:#f92672">=</span> list(player[<span style="color:#e6db74">&#39;heroes&#39;</span>])
</span></span><span style="display:flex;"><span>    name_spells <span style="color:#f92672">=</span> list(player[<span style="color:#e6db74">&#39;spells&#39;</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    outfile <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;../content/players/&#39;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;.md&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> open(outfile, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> fw:
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;---</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;title: &#39;Detail info &#34;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;&#39;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;coc_css: true </span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;aliases: [/players/&#34;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;]</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_name: &#34;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_tag: </span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">+</span> name_tag <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\&#34;\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_explevel: &#34;</span> <span style="color:#f92672">+</span> str(name_explevel) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_thlevel: &#34;</span> <span style="color:#f92672">+</span> str(name_thlevel) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         <span style="color:#66d9ef">if</span> name_townhallweaponlevel:
</span></span><span style="display:flex;"><span>            fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_townhallweaponlevel: &#34;</span> <span style="color:#f92672">+</span> str(name_townhallweaponlevel) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_trophies: &#34;</span> <span style="color:#f92672">+</span> str(name_trophies) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_besttrophies: &#34;</span> <span style="color:#f92672">+</span> str(name_besttrophies) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_warstars: &#34;</span> <span style="color:#f92672">+</span> str(name_warstars) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_attackwins: &#34;</span> <span style="color:#f92672">+</span> str(name_attackwins) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_defensewins: &#34;</span> <span style="color:#f92672">+</span> str(name_defensewins) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_bhlevel: &#34;</span> <span style="color:#f92672">+</span> str(name_bhlevel) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_versustrophies: &#34;</span> <span style="color:#f92672">+</span> str(name_versustrophies) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_bestversustrophies: &#34;</span> <span style="color:#f92672">+</span> str(name_bestversustrophies) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_versusbattlewins: &#34;</span> <span style="color:#f92672">+</span> str(name_versusbattlewins) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_versusbattlewincount: &#34;</span> <span style="color:#f92672">+</span> str(name_versusbattlewincount) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_donations: &#34;</span> <span style="color:#f92672">+</span> str(name_donations) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_donationsreceived: &#34;</span> <span style="color:#f92672">+</span> str(name_donationsreceived) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_role: &#34;</span> <span style="color:#f92672">+</span> name_role <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_achievements: &#34;</span> <span style="color:#f92672">+</span> str(name_achievements) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_clan: &#34;</span> <span style="color:#f92672">+</span> str(name_clan) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         <span style="color:#66d9ef">if</span> name_league:
</span></span><span style="display:flex;"><span>            fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_league: &#34;</span> <span style="color:#f92672">+</span> str(name_league) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_troops: &#34;</span> <span style="color:#f92672">+</span> str(name_troops) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_heroes: &#34;</span> <span style="color:#f92672">+</span> str(name_heroes) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_spells: &#34;</span> <span style="color:#f92672">+</span> str(name_spells) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;---</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;\{\{&lt; clan_player_structure url=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">+</span> json_file_all <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74"> &gt;\}\}</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>         fw<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    outfile_bb <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;../content/builderbase/&#39;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;.md&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> open(outfile_bb, <span style="color:#e6db74">&#34;w&#34;</span>) <span style="color:#66d9ef">as</span> fbb:
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;---</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;title: &#39;Builder Base info for &#34;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;&#39;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;coc_css: true </span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;aliases: [/builderbase/&#34;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;]</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_name: &#34;</span> <span style="color:#f92672">+</span> name <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_tag: </span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">+</span> name_tag <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\&#34;\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_explevel: &#34;</span> <span style="color:#f92672">+</span> str(name_explevel) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_bhlevel: &#34;</span> <span style="color:#f92672">+</span> str(name_bhlevel) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_versustrophies: &#34;</span> <span style="color:#f92672">+</span> str(name_versustrophies) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_bestversustrophies: &#34;</span> <span style="color:#f92672">+</span> str(name_bestversustrophies) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_versusbattlewins: &#34;</span> <span style="color:#f92672">+</span> str(name_versusbattlewins) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_versusbattlewincount: &#34;</span> <span style="color:#f92672">+</span> str(name_versusbattlewincount) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_donations: &#34;</span> <span style="color:#f92672">+</span> str(name_donations) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_donationsreceived: &#34;</span> <span style="color:#f92672">+</span> str(name_donationsreceived) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;player_role: &#34;</span> <span style="color:#f92672">+</span> name_role <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_achievements: &#34;</span> <span style="color:#f92672">+</span> str(name_achievements) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_clan: &#34;</span> <span style="color:#f92672">+</span> str(name_clan) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> name_league:
</span></span><span style="display:flex;"><span>           fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_league: &#34;</span> <span style="color:#f92672">+</span> str(name_league) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_troops: &#34;</span> <span style="color:#f92672">+</span> str(name_troops) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_heroes: &#34;</span> <span style="color:#f92672">+</span> str(name_heroes) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;dict_spells: &#34;</span> <span style="color:#f92672">+</span> str(name_spells) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;---</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;\{\{&lt; clan_player_base_structure url=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">+</span> json_file_all <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74"> &gt;\}\}</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>write(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        fbb<span style="color:#f92672">.</span>close()
</span></span></code></pre></div>]]></content:encoded></item><item><title>Mailcow Grafana Dashboard</title><link>/posts/2021-05-13-mailcow-grafana/</link><pubDate>Thu, 13 May 2021 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2021-05-13-mailcow-grafana/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;For a while now, my hosting company JR-Hosting, was stopped. But I still needed a way to host my mailboxes
and that of several other domains that I own. It was brought to my attention that while playing with Docker
I could combine that with a mailserver setup. Mailcow, or better Mailcow Dockerized. As there was a previous
version which did not use Docker :-).&lt;/p&gt;
&lt;p&gt;The setup is trivial &lt;a href="https://mailcow.github.io/mailcow-dockerized-docs/i_u_m_install/"&gt;Installation link&lt;/a&gt;
or at least it was for me. I used one of the components before, at home, for JRHosting and at work where
I created the foundation for the currently still in use rspamd setup at work.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>For a while now, my hosting company JR-Hosting, was stopped. But I still needed a way to host my mailboxes
and that of several other domains that I own. It was brought to my attention that while playing with Docker
I could combine that with a mailserver setup. Mailcow, or better Mailcow Dockerized. As there was a previous
version which did not use Docker :-).</p>
<p>The setup is trivial <a href="https://mailcow.github.io/mailcow-dockerized-docs/i_u_m_install/">Installation link</a>
or at least it was for me. I used one of the components before, at home, for JRHosting and at work where
I created the foundation for the currently still in use rspamd setup at work.</p>
<h3 id="mailcow">Mailcow</h3>
<p>Mailcow consists of several helper programs, like rspamd for antispam filtering, dovecot for hosting the
mailboxes (storage and delivery/filtering), postfix for the actual mailserver, nginx and php-fpm for the
webpages that are hosted on the platform.</p>
<h3 id="rspamd">Rspamd</h3>
<p>As mentioned one of the applications is rspamd, this is a fast and modulair anti spam system that uses
several modules and feedback from those modules to define a score to a message. It&rsquo;s not soley bayes or
rbls that form the strength of rspamd. It is written by Vsevolod Stakhov, a hardliner when encountering
things that are just not right (outspoken), but a very pragmatic programmer. You need to convince him when
there is an error, you will not always get an easy time, but given my years of seeing him in action, he
always evaluates your feedback and if found proven he will fix the issues.</p>
<h3 id="grafana">Grafana</h3>
<p>One of the things that are lacking in all applications, is a dashboard for the environment. And truth
to be told, you can create a dashboard for the individual applications, some of them even support
prometheus exporter output. But there is no general way to see the individual state. And this is
likely not to come, everyone&rsquo;s needs are different. What works for me is an specific overview, and
what works for you is another specific overview. Those might not align or even look a like.</p>
<h4 id="designing-my-own">Designing my own</h4>
<p>So, there is this thing called &lsquo;mailcow-exporter&rsquo;, I use this one from Docker: thej6s/mailcow-exporter
which reads your mailcow parameters via the API and exposes them as prometheus understandable format.</p>
<p>I then started experimenting with Grafana, I know the tool enough for my needs but I like to play around
every now and then to make it even better. I am not graphically oriented though so there are things that
might be better.</p>
<p>It worked out for the following dashboard, note that I set the timer on &ldquo;5&rdquo; minutes for some of the graphs
because I used updated my mailcow installation and some containers where counted twice :-) :</p>
<p><img loading="lazy" src="/images/mailcow-dashboard.png" type="" alt="Mailcow Dashboard"  /></p>
<h2 id="want-more">Want more?</h2>
<p>If you are interested in the above dashboard, poke me and we&rsquo;ll see how we can arrange for you to have
the dashboard. If you have suggestions, please let me know as well!</p>
<p>You can also find the direct json file here:
<a href="https://github.com/remkolodder/mailcow-dashboard">https://github.com/remkolodder/mailcow-dashboard</a></p>
]]></content:encoded></item><item><title>Sue Student Edition 2021</title><link>/posts/2021-04-22-sue-student-edition/</link><pubDate>Thu, 22 Apr 2021 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2021-04-22-sue-student-edition/</guid><description>&lt;p&gt;Today my first Sue Student Edition happened. Well not entirely true, it happened a few times already
and I was present most of the times. But today was somewhat special. I gave the keynote before my
coworker Tijmen did a presentation + workshop. And I recapped the event afterwards.&lt;/p&gt;
&lt;h2 id="digital-edition"&gt;Digital edition&lt;/h2&gt;
&lt;p&gt;As Covid is still around very much and restrictions are in place all over, we needed to host this event
on our digital platform. For me that was the first time doing a talk on a digital event. Ofcourse as
coach for Sue I do this daily, but not as speaker with polls and such. It is good that we practised a
bit upfront to be familiar with the tools.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Today my first Sue Student Edition happened. Well not entirely true, it happened a few times already
and I was present most of the times. But today was somewhat special. I gave the keynote before my
coworker Tijmen did a presentation + workshop. And I recapped the event afterwards.</p>
<h2 id="digital-edition">Digital edition</h2>
<p>As Covid is still around very much and restrictions are in place all over, we needed to host this event
on our digital platform. For me that was the first time doing a talk on a digital event. Ofcourse as
coach for Sue I do this daily, but not as speaker with polls and such. It is good that we practised a
bit upfront to be familiar with the tools.</p>
<p>One of my main worries was that with live audience you can influence the setting a lot. You can interact
with people and see the audience and whether they pay attention or fell asleep. With a digital audience you
cannot see those signs that easily. People are not required to enable their camera for example, so things
can be hidden from you. People can watch different things and pretend that they are watching your talk.</p>
<p>But, at the start we had a good conversation with people from the audience and we talked about the difference
between live and digital education. They too find it more difficult to interact with others during digital
sessions. Ofcourse there are benefits as well. You can sleep a bit longer because no need to travel.</p>
<h2 id="the-talks">The talks</h2>
<p>I gave a talk about &ldquo;Infrastructure and security&rdquo;, zooming in on Security Essentials and using the C-I-A triad.
By using several polls I tried to interact with the audience, which worked out above expectation. Since I have
long term experience in the field I could use several examples from actual situations and found a few more
on the internet which I used in the slides as well. During the talk we had several discussions with the audience
which made the talk interactive. Thank you for that to the audience! I went a bit over time because of that and
I think it is awesome to have such interaction. I was allowed to introduce my colleague Tijmen who took over the
talk and went in depth with security concepts, tools and offered an actual hacking workshop with selfmade boxes.</p>
<h2 id="thanks">Thanks</h2>
<p>I think the event was a succes and feedback from the audience suggested that as well. It could not have been such
a success without the help and support of Sue B.V., Laura, Tijmen, Koen, Patrick, Raimond and ofcourse the
audience. I hope that I will be able to attend and/or talk at such an event again and hopefully meet you again
where we can discuss during a drink.</p>
]]></content:encoded></item><item><title>Emerging Threats Python script</title><link>/posts/2020-09-26-emerging-threats-python/</link><pubDate>Sat, 26 Sep 2020 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2020-09-26-emerging-threats-python/</guid><description>&lt;p&gt;For some time I am a perl fan, but perl is not that popular anymore so I
decided to try and see whether I can use Python as well. After moving my
systems from FreeBSD to Ubuntu and Debian (proxmox needs it) I also used the
&amp;rsquo;emerging-ipset-update.pl&amp;rsquo; script to drop emerging treats as soon as possible.&lt;/p&gt;
&lt;h2 id="python"&gt;Python&lt;/h2&gt;
&lt;p&gt;After or rather while following the Udemy&amp;rsquo;s &amp;lsquo;2020 Complete Python bootcamp:
From Zero to Hero&amp;rsquo; by Jose Portilla (Hi!) I decided that I could rewrite the
perl script into python. And so I did. Below is the version that resulted from
that effort. It can surely be smarter, so poke me on my email address if that
is possible and I&amp;rsquo;ll update this. Thanks Jose for your great course! I
appreciate it!&lt;/p&gt;</description><content:encoded><![CDATA[<p>For some time I am a perl fan, but perl is not that popular anymore so I
decided to try and see whether I can use Python as well. After moving my
systems from FreeBSD to Ubuntu and Debian (proxmox needs it) I also used the
&rsquo;emerging-ipset-update.pl&rsquo; script to drop emerging treats as soon as possible.</p>
<h2 id="python">Python</h2>
<p>After or rather while following the Udemy&rsquo;s &lsquo;2020 Complete Python bootcamp:
From Zero to Hero&rsquo; by Jose Portilla (Hi!) I decided that I could rewrite the
perl script into python. And so I did. Below is the version that resulted from
that effort. It can surely be smarter, so poke me on my email address if that
is possible and I&rsquo;ll update this. Thanks Jose for your great course! I
appreciate it!</p>
<h2 id="the-script">The script</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## 25/09/2020:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Python based E.T parser by Remko Lodder &lt;remko@elvandar.org&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">##</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## The script is based on the original perl script by Joshua Gimer and an unknown author who created</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## my reference version: https://doc.emergingthreats.net/pub/Main/EmergingFirewallRules/emerging-ipset-update.pl.txt</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">##</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## The netaddr functionality in the form of IPNetwork and cidr merge</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## are taken from the website: http://www.korznikov.com/2014/08/creating-black-list-of-ips-for-iptables.html</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## Thank you for the pointers there, which I shamelessly used to create this variant.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">##</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## The script fetches the ip addreses/ranges that are potential treats and creates an ipset list from it.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## The ipset list is then used by iptables to produce a working firewall set.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> time
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> urllib.request
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> re
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> syslog
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> netaddr <span style="color:#f92672">import</span> <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> socket <span style="color:#f92672">import</span> timeout
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> urllib.request <span style="color:#f92672">import</span> Request, urlopen
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> urllib.error <span style="color:#f92672">import</span> URLError, HTTPError
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Prototype variables</span>
</span></span><span style="display:flex;"><span>n <span style="color:#f92672">=</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Bootup messaging and syslog</span>
</span></span><span style="display:flex;"><span>syslog<span style="color:#f92672">.</span>openlog(logoption<span style="color:#f92672">=</span>syslog<span style="color:#f92672">.</span>LOG_PID, facility<span style="color:#f92672">=</span>syslog<span style="color:#f92672">.</span>LOG_INFO)
</span></span><span style="display:flex;"><span>syslog<span style="color:#f92672">.</span>syslog(<span style="color:#e6db74">&#39;Starting Emerging Threats (ET) IPTables update script....&#39;</span>)
</span></span><span style="display:flex;"><span>print (<span style="color:#e6db74">&#39;Starting Emerging Threats (ET) IPTables update script....&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Two times a day timer</span>
</span></span><span style="display:flex;"><span>timer <span style="color:#f92672">=</span> <span style="color:#ae81ff">43200</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Sleep a bit after an timeout.</span>
</span></span><span style="display:flex;"><span>timeout_timer <span style="color:#f92672">=</span> <span style="color:#ae81ff">120</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># The location of the Emerging Threats revison number file.</span>
</span></span><span style="display:flex;"><span>emerging_root <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://rules.emergingthreats.net/fwrules&#39;</span>
</span></span><span style="display:flex;"><span>emerging_fwrev <span style="color:#f92672">=</span> emerging_root <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/FWrev&#39;</span>
</span></span><span style="display:flex;"><span>emerging_fwrules <span style="color:#f92672">=</span> emerging_root <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/emerging-Block-IPs.txt&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Temporary files</span>
</span></span><span style="display:flex;"><span>tmp_dir <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;/tmp&#39;</span>
</span></span><span style="display:flex;"><span>rules_file <span style="color:#f92672">=</span> tmp_dir <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/emerging_iptables2.txt&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Binary location</span>
</span></span><span style="display:flex;"><span>iptables <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;/sbin/iptables&#39;</span>
</span></span><span style="display:flex;"><span>ipset <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;/sbin/ipset&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Iptable chains</span>
</span></span><span style="display:flex;"><span>iptables_att_chain <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;ATTACKERS&#39;</span>
</span></span><span style="display:flex;"><span>iptables_drop_chain <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;ETLOGDROP&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ipset names</span>
</span></span><span style="display:flex;"><span>ipset_botccnet <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;botccnet&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get the current IPTables ruleset revison number.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_fw_rev</span>():
</span></span><span style="display:flex;"><span>    response <span style="color:#f92672">=</span> urllib<span style="color:#f92672">.</span>request<span style="color:#f92672">.</span>urlopen(emerging_fwrev)
</span></span><span style="display:flex;"><span>    data <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>read()
</span></span><span style="display:flex;"><span>    text <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>decode(<span style="color:#e6db74">&#39;utf-8&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> text
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get the firewall rules and ignore the errors</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_fw_rules</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">with</span> urllib<span style="color:#f92672">.</span>request<span style="color:#f92672">.</span>urlopen(emerging_fwrules) <span style="color:#66d9ef">as</span> response, open(rules_file, <span style="color:#e6db74">&#39;wb&#39;</span>) <span style="color:#66d9ef">as</span> out_file:
</span></span><span style="display:flex;"><span>            data <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>read() <span style="color:#75715e"># a `bytes` object</span>
</span></span><span style="display:flex;"><span>            out_file<span style="color:#f92672">.</span>write(data)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> HTTPError <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;There was an error: &#39;</span>, e<span style="color:#f92672">.</span>code)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">pass</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> URLError <span style="color:#66d9ef">as</span> e:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;Something went wrong in reaching the server: &#39;</span>, e<span style="color:#f92672">.</span>reason)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">pass</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">ConnectionResetError</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;---&gt; Connection reset, retrying in &#39;</span> <span style="color:#f92672">+</span> timeout_timer)
</span></span><span style="display:flex;"><span>        time<span style="color:#f92672">.</span>sleep (timeout_timer)
</span></span><span style="display:flex;"><span>        process_et_rules()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">pass</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> timeout:
</span></span><span style="display:flex;"><span>        print(<span style="color:#e6db74">&#39;---&gt; Connection timed out, retrying in &#39;</span> <span style="color:#f92672">+</span> timeout_timer)
</span></span><span style="display:flex;"><span>        time<span style="color:#f92672">.</span>sleep (timeout_timer)
</span></span><span style="display:flex;"><span>        process_et_rules()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">pass</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Be able to fork a process</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">parent_child</span>():
</span></span><span style="display:flex;"><span>    n <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>fork()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># If N is &gt;0 then a child had not yet been forked (we are in master process)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> n <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Setup first tables, they will not be readded later on.</span>
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -N &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -N &#39;</span> <span style="color:#f92672">+</span> iptables_drop_chain)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Flush previously assigned iptables and ipset parameters</span>
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -F &#39;</span> <span style="color:#f92672">+</span> iptables_drop_chain)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -F &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -D FORWARD -j &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -D INPUT -j &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Create new iptables and ipset parameters</span>
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -I FORWARD 1 -j &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -I INPUT 1 -j &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -A &#39;</span> <span style="color:#f92672">+</span> iptables_drop_chain <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -j LOG --log-prefix &#34;ET BLOCK: &#34;&#39;</span>)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -A &#39;</span> <span style="color:#f92672">+</span> iptables_drop_chain <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -j DROP&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Remove current ipset list, and recreate it.</span>
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(ipset <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -X &#39;</span> <span style="color:#f92672">+</span> ipset_botccnet)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(ipset <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -N &#39;</span> <span style="color:#f92672">+</span> ipset_botccnet <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; nethash&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Create the ipset matching chain</span>
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -A &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -p ALL -m set --match-set &#39;</span> <span style="color:#f92672">+</span> ipset_botccnet <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; src,src -j &#39;</span> <span style="color:#f92672">+</span> iptables_drop_chain)
</span></span><span style="display:flex;"><span>        os<span style="color:#f92672">.</span>system(iptables <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -A &#39;</span> <span style="color:#f92672">+</span> iptables_att_chain <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -p ALL -m set --match-set &#39;</span> <span style="color:#f92672">+</span> ipset_botccnet <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; dst,dst -j &#39;</span> <span style="color:#f92672">+</span> iptables_drop_chain)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># else if N = 0 then that means that we are in child mode and can start processing the et rules.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>        process_et_rules()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">process_et_rules</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Reset revision number before starting.</span>
</span></span><span style="display:flex;"><span>    ip_list <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    rev_num <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>        old_rev_num <span style="color:#f92672">=</span> rev_num
</span></span><span style="display:flex;"><span>        rev_num <span style="color:#f92672">=</span> get_fw_rev()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> int(rev_num) <span style="color:#f92672">&gt;</span> int(old_rev_num):
</span></span><span style="display:flex;"><span>            get_fw_rules()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># loop through rules file, remove empty lines and process the remaining ip</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># by using a regular expression that matches &lt;wordboundary&gt;ipaddress&lt;wordboundary&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">with</span> open(rules_file, <span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> read_file:
</span></span><span style="display:flex;"><span>                lines <span style="color:#f92672">=</span> [line <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> read_file<span style="color:#f92672">.</span>readlines() <span style="color:#66d9ef">if</span> line<span style="color:#f92672">.</span>strip()]
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> lines:
</span></span><span style="display:flex;"><span>                    line <span style="color:#f92672">=</span> line<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> re<span style="color:#f92672">.</span>findall(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/?[0-9]{1,2}?\b&#39;</span>,line):
</span></span><span style="display:flex;"><span>                        ip_list <span style="color:#f92672">+=</span> [IPNetwork(line)]
</span></span><span style="display:flex;"><span>            ip_list <span style="color:#f92672">=</span> cidr_merge(ip_list)
</span></span><span style="display:flex;"><span>            amount_addresses <span style="color:#f92672">=</span> len(ip_list)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Flush entries in the ipset list.</span>
</span></span><span style="display:flex;"><span>            os<span style="color:#f92672">.</span>system(ipset <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; flush &#39;</span> <span style="color:#f92672">+</span>  ipset_botccnet)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> ip_address <span style="color:#f92672">in</span> ip_list:
</span></span><span style="display:flex;"><span>                <span style="color:#75715e"># Add new entries from the list.</span>
</span></span><span style="display:flex;"><span>                os<span style="color:#f92672">.</span>system(ipset <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; -A &#39;</span> <span style="color:#f92672">+</span>  ipset_botccnet <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; &#39;</span> <span style="color:#f92672">+</span> str(ip_address))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Summarize what we did, so that we have an idea how many addresses should be there.</span>
</span></span><span style="display:flex;"><span>            syslog<span style="color:#f92672">.</span>syslog(<span style="color:#e6db74">&#34;Wrote </span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> addresses to the ipset list, using ET version </span><span style="color:#e6db74">%d</span><span style="color:#e6db74">, going to sleep....&#34;</span> <span style="color:#f92672">%</span> (amount_addresses,int(rev_num)))
</span></span><span style="display:flex;"><span>            time<span style="color:#f92672">.</span>sleep (timer)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            syslog<span style="color:#f92672">.</span>syslog(<span style="color:#e6db74">&#34;The old version: </span><span style="color:#e6db74">%d</span><span style="color:#e6db74"> is the same as the current version: </span><span style="color:#e6db74">%d</span><span style="color:#e6db74">... sleeping a bit and retry....&#34;</span> <span style="color:#f92672">%</span> (int(old_rev_num), int(rev_num)))
</span></span><span style="display:flex;"><span>            time<span style="color:#f92672">.</span>sleep (timer)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># If a child is not yet found, fork</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> n <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>    parent_child()
</span></span></code></pre></div>]]></content:encoded></item><item><title>The end of JR-Hosting</title><link>/posts/2020-03-19-jrhosting-ended/</link><pubDate>Thu, 19 Mar 2020 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2020-03-19-jrhosting-ended/</guid><description>&lt;p&gt;At the end of the month, my company JR-Webscripting en Hosting, will cease to exist. Justin and
myself thought things over and with changes in our lives in the entire last year(s), we decided
that we needed to spend our time differently. The time and effort of running your own business in
the (flooded) hosting market is extremely difficult. You need scale, and offer an interesting price.
And it will cost you countless hours. For us, it was worth this effort for almost 15 years, but
the balance was lost not that long ago. While our reasons where not in the financial sector
(In my opinion, the main reason for stopping most businesses), it does play along. The margins are
small with our pricing scheme, and the benefits are low. Too low factually. What weight most for
me was the investment of time that I can no longer realise that easily.&lt;/p&gt;</description><content:encoded><![CDATA[<p>At the end of the month, my company JR-Webscripting en Hosting, will cease to exist. Justin and
myself thought things over and with changes in our lives in the entire last year(s), we decided
that we needed to spend our time differently. The time and effort of running your own business in
the (flooded) hosting market is extremely difficult. You need scale, and offer an interesting price.
And it will cost you countless hours. For us, it was worth this effort for almost 15 years, but
the balance was lost not that long ago. While our reasons where not in the financial sector
(In my opinion, the main reason for stopping most businesses), it does play along. The margins are
small with our pricing scheme, and the benefits are low. Too low factually. What weight most for
me was the investment of time that I can no longer realise that easily.</p>
<p>So, since the beginning of october we decided to inform our customers and ask them to move to another
provider at the end of the contract. All our customers migrated quickly and recently we migrated away
the last customer. That marked the end of the hosting servers, we have them on shutdown and they will
be removed in a little. The site is still available as placeholder on my host and mail for the domain
will still be incoming for a little more.</p>
<p>For our customers and interested parties: Thank you for the trust in the past 15 years! It had been
a blast.</p>
]]></content:encoded></item><item><title>In Memoriam Freddy Mercury 5-9-1946 - 24-11-1991</title><link>/posts/2019-11-24-im-freddy-mercury/</link><pubDate>Sun, 24 Nov 2019 23:09:32 +0000</pubDate><guid>https://www.evilcoder.org/posts/2019-11-24-im-freddy-mercury/</guid><description>&lt;p&gt;On my old blog, I tried to remember the passing of Freddy Mercury periodically.
I failed to do that for several years. Freddy, wherever you may be, rest in peace.
I still remember your music and listen to it very often. I visited the concerts
of the remaining band members of Queen, it&amp;rsquo;s still amazing, even without you.&lt;/p&gt;
&lt;p&gt;One day, we will meet!&lt;/p&gt;
&lt;p&gt;Image used from express.co.uk and referenced from them:
&lt;img loading="lazy" src="https://cdn.images.express.co.uk/img/dynamic/35/590x/Freddie-Mercury-death-His-final-hours-1208498.jpg?r=1574550506127" alt="Freddy Mercury" title="Freddy Mercury" /&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>On my old blog, I tried to remember the passing of Freddy Mercury periodically.
I failed to do that for several years. Freddy, wherever you may be, rest in peace.
I still remember your music and listen to it very often. I visited the concerts
of the remaining band members of Queen, it&rsquo;s still amazing, even without you.</p>
<p>One day, we will meet!</p>
<p>Image used from express.co.uk and referenced from them:

  <img loading="lazy" src="https://cdn.images.express.co.uk/img/dynamic/35/590x/Freddie-Mercury-death-His-final-hours-1208498.jpg?r=1574550506127" alt="Freddy Mercury"  title="Freddy Mercury"  /></p>
]]></content:encoded></item><item><title>NLUUG 2019 Najaarsconferentie</title><link>/posts/2019-11-22-nluug-2019/</link><pubDate>Fri, 22 Nov 2019 23:09:32 +0000</pubDate><guid>https://www.evilcoder.org/posts/2019-11-22-nluug-2019/</guid><description>&lt;p&gt;As the title states (in dutch sorry it&amp;rsquo;s the original name, but you can translate it to: autumn conference NLUUG2019), I was at the NLUUG 2019.
My last visit with Snow (now Sue) was when it was still in &amp;ldquo;De Reehorst&amp;rdquo; in Ede. Which appeared to have been a few years ago. One of the
reasons for not visiting it more often is that I was soo much into FreeBSD that I didn&amp;rsquo;t look around that much. That changed this year
when I stopped volunteering for FreeBSD. I got my RHCSA and RHCE this year and as technical field manager I decided to show my face and talk with
people (and learn something myself as well).&lt;/p&gt;</description><content:encoded><![CDATA[<p>As the title states (in dutch sorry it&rsquo;s the original name, but you can translate it to: autumn conference NLUUG2019), I was at the NLUUG 2019.
My last visit with Snow (now Sue) was when it was still in &ldquo;De Reehorst&rdquo; in Ede. Which appeared to have been a few years ago. One of the
reasons for not visiting it more often is that I was soo much into FreeBSD that I didn&rsquo;t look around that much. That changed this year
when I stopped volunteering for FreeBSD. I got my RHCSA and RHCE this year and as technical field manager I decided to show my face and talk with
people (and learn something myself as well).</p>
<p>To quickly summarize the day: It was great but exhausting :-).. numberous talks with coworkers but also old friends (Hi Johan, Rene, Ronny, Alain, Cor, I mean you for
example!). For me this was a first time as technical field manager at a conference. The SUE team was the biggest team of them all I think so that was great to see.
Thank you all coworkers for your visit and your big enthusiasm. I hope that we can visit many more of these conferences!</p>
<p>About the talks:</p>
<p>The keynote (by David Blank-Edelman) at first was very interesting and eye-opening. Not only because of the talk, but also because of the employer of David, Microsoft. A while ago I found
that MS was seen more and more on open-source territory and I think this demonstrates that I am right about that. Nevertheless we got a serious talk about Site Reliability
Engineering and what angles you can look at it. The main thing that David demonstrated is that &ldquo;it depends&rdquo;. You need to find a way to first describe your company and
wishes, but also your clients. Reliability in this regard is measured at the client instead of internal monitoring/metric gathering. For example if you have 100 servers
and a few of them go down, are you in panic ? It depends! If the customer doesn&rsquo;t notice anything, just keep chilling. If the client does notice anything, like not being
able to access your site, or add items to the shopping cart.. then make sure it starts working again! You need to define SLI and SLO&rsquo;s (Service Level indicators, which
metrics at what &ldquo;item&rdquo; in the business chain and Service Level Objectives: How do you want things to perform, what is your goal). I think you can do that with proper setup
monitoring that not only checks availability but also does an actual login, or an actual shopping cart experience. When I worked for a major ISP in .NL they did that with
Selenium. The setup replicated a user login from various remote places.</p>
<p>My next talk was about making scripts better by my old coworker and friend Michael Boelen. I enjoyed this talk, I am experienced in writing shell scripts, but still I
learned a few things that I didnt know before. I also understand better on how to approach creating new scripts and what the caveats are for being posix compliant. Michael
had the crowd on his hand, he got a very interactive talk and still keeping track on the objective from his talk. It was appreciated Michael!</p>
<p>After that I visited the talk about &ldquo;treating documentation as code&rdquo; by Hagen Bauer. I visited that with in my huge documentation experience from FreeBSD. Hagen has a setup
where he uses asciidoctor and some modifications to print his documentation in various formats. It can use external input as well as just regular &lsquo;md&rsquo; kind of files. So
once prepared, you can more easily write documentation just in ascci/plain text and if you have set it up with a CI/CD pipeline for example you can auto generate new documentation
when you have a commit or change in your GIT repo. I think this lowers the barrier if there is a template and all you need to do is fill in an text file (With some markup).
If I ever have time, this is really interesting to understand better!</p>
<p>After Hagen&rsquo;s talk we had lunch. There were many things to choose from, a dangerous approach because of an after lunch dip risk.</p>
<p>Next up after the lunch was a talk from Koen de Jonge, board member for NLUUG. His talk was about a dream or idea: Community Hosted Open Source Infrastructure. (CHOSI.org).
This dream started with how we (I include myself there) used to learn and do things, touch real hardware, modify kernels, wait ages for an kernel or program compilation
completed and noticing that you made a mistake. Where nowadays people &ldquo;take care&rdquo; of you. The educational value of having in-depth knowledge about products is going away.
The cloud offers items that you dont have to touch at all. Ofcourse the cloud infra people need to do so, but that group of people is being reduced in the world. See for
example RHCE8, you learn ansible.. which is a great and fun thing to do, but you dont learn in-depth technical hacks on the commandline anymore. With this talk Koen tried
to take the audience to a world which we knew from the past and bring that back. The general idea is to have at least one or more racks with own equipment which you can use
to start all kinds of vm&rsquo;s, from linux to a bsd to solaris. With a &ldquo;bierviltje&rdquo; calculation he noticed the required funding which would be approx 12 euro per user. I am
very much interested!</p>
<p>Further down the road I visited the XS4all moet blijven talk from Anco Scholte ter Horst, current CEO of &ldquo;Freedom Internet&rdquo; (The new XS4all). Anco took us down the road
which was followed after announcing that XS4all needs to be assimilated by KPN. He told us about the fight they have put up to save the company, to see alternatives and
finally after another reply from KPN that they were going to assimilate XS4all and drop the company behind it, the birth of Freedom Internet. A very nice and driven talk
from Anco, the crowd was very interested including myself. Lets see where this is heading and hopefully they can do what they want to do!</p>
<p>Next in line I visited &ldquo;what does vNUMA actually mean?&rdquo; by Wim ten Have. Wim appears to be extremely technical and possesses knowledge that not many people have.
During this track I was from time to time very lost. Not because Wim didn&rsquo;t explain, but because I didn&rsquo;t cope the in depth knowledge. NUMA is known for making processors
able to directly access memory regions. In order to use that effectively you should combine CPU&rsquo;s that are near each other and share the same NUMA domain. vNUMA is an
addition to QEMU / KVM and enables automatic mapping of NUMA within a VM basically. (Someone correct me if I am wrong ;-)). Sadly, if you are not that experienced
you will loose much of the information presented.</p>
<p>Just before attending the last talk, I joined Martin Geusebroek&rsquo;s talk about &ldquo;Counter Social Engineering&rdquo;. Martin is an experienced HUMINT officer and extremely knowledgeable
about this subject. He gave multiple examples on how things work. There were a few demo&rsquo;s / recordings of social engineering stuff that actually worked. Next to that he
also did a &ldquo;remember the given names&rdquo;, where he did try to influence your brain. In the recap we were presented the given names. One of them was not in the list but your
head thought it was, because a lot of the words shared the same topic. Your brain fills in the details. That makes social engineers able to influence you without you actually
realising. When I worked at ING we had such trainings periodically and then for entire days. Always think who asks what and why. Try to properly verify someone&rsquo;s identity and
when in doubt, just get help from senior management. Even if the director is giving you a hard time, it&rsquo;s in his companies best interest if you are very firm and solid in your
work. Thanks Martin for bringing this topic to NLUUG!</p>
<p>and finally the closing keynote:
Tales (Fails) from the trenches… by Edwin den Andel. Edwin is a very classic hacker, in the sense that the name hacker was actually ment. Edwin is creative and thinks out of
the box to try and obtain information. Not for the worse but for the better. Nowadays hackers are seen as nerds that break into computers and steal stuff. Edwin and Zerocopter
behind him try to address that. They want to receive vulnerability information and highly suggest that you do not download entire datasets, but just one or two rows to proof
that you can access data. Else you will get into the dark mazes of the law and you might even be prosecuted. Next to advocating the right thing, Edwin also gave numberous
examples on how companies failed. I felt really connected with this topic and I think a lot of people found this the best talk of the day. Edwin easily presents his knowledge
and is easy to follow. Edwin, I enjoyed your talk a lot, thank you!</p>
<p>After all these talks people needed drinks and beverages. My employer SUE sponsored these and a lot of people stayed until it was time to wrap up.
Together with a few coworkers from SUE, we were the last ones to leave the conference. I hope to be able to rejoin the NLUUG conference next year, either in my current
role or in a new role.</p>
]]></content:encoded></item><item><title>Aandacht voor de leraar</title><link>/posts/2019-11-08-aandacht-voor-de-leraar/</link><pubDate>Fri, 08 Nov 2019 23:09:32 +0000</pubDate><guid>https://www.evilcoder.org/posts/2019-11-08-aandacht-voor-de-leraar/</guid><description>&lt;p&gt;Voor Sue is het opleiden van onze mensen belangrijk. Wij zorgen er dan ook voor dat onze collega&amp;rsquo;s door ons (Fieldmanagers) bezocht worden, om zaken op technisch vlak maar
ook opleidings vlak door te spreken en regelmatig te plannen waarmee en hoe iemand zich wil ontwikkelen. Vervolgens maken we dat als bedrijf mogelijk door opleidingen te
faciliteren, studiedagen al contractueel beschikbaar te stellen, de kosten te betalen van de opleiding EN de certificering.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Voor Sue is het opleiden van onze mensen belangrijk. Wij zorgen er dan ook voor dat onze collega&rsquo;s door ons (Fieldmanagers) bezocht worden, om zaken op technisch vlak maar
ook opleidings vlak door te spreken en regelmatig te plannen waarmee en hoe iemand zich wil ontwikkelen. Vervolgens maken we dat als bedrijf mogelijk door opleidingen te
faciliteren, studiedagen al contractueel beschikbaar te stellen, de kosten te betalen van de opleiding EN de certificering.</p>
<p>In het bedrijfsleven, werkende voor Sue, ben ik dus gewend dat er een goede strikte focus is op ontwikkeling en opleiding.</p>
<p>Wij verzorgen regelmatig opleidingen in-house, en geven die of zelf met onze gecertificeerde collega&rsquo;s of er komt een leraar extern binnen om de opleiding te geven. De
groepen worden niet te groot gemaakt zodat een ieder de benodigde aandacht kan krijgen. Tijdens onze studie dagen/weken is het mogelijk om savonds te blijven mee eten
en met elkaar kennis te kunnen delen. Wij staan dus voor een kwalitatief hoogwaardig leertraject.</p>
<p>Mijn vrouw is leerkracht, een werkgebied waar ze voor gekozen heeft om onze toekomst (de mensen die ons straks moeten onderhouden!) te begeleiden en op te kunnen leiden
zodat ze een fijne periode in de maatschappij kunnen hebben, hun steentje kunnen bijdragen en goed opgeleid ten tonele komen.</p>
<p>Ik had verwacht dat in het onderwijs, waar de dienst &ldquo;het opleiden van onze toekomst&rdquo; is, een veel grotere focus heeft dan het commerciele bedrijfsleven.
Helaas is niets minder waar. Er zijn heel veel administratieve handelingen. Er komen steeds meer leerlingen in een groep, waardoor persoonlijke aandacht bijna niet realiseerbaar is.
Er moeten ontzettend veel zaken buiten schooltijd worden gedaan en geregeld. Ouders worden steeds mondiger en verwachten meer en meer van de leraar, ze worden zelfs boos als
het kind in kwestie niet op het niveau scoort die ze graag zien. Door al die zaken is het in mijn ogen ontzettend moeilijk om kwalitatief hoogwaardig onderwijs te kunnen leveren.</p>
<p>Ondanks de grote hoeveelheid werk en taken, is er een enorme onderwaarding voor het werk, een fulltime docent werkt in mijn ogen minstens 50% buiten schooltijd (geen lestijd!) extra.
We hebben het dan over dik 60 uur per week. In het bedrijfsleven zouden deze leraren daar een goed salaris en voorzieningen voor krijgen (het is immers de primaire dienst). In plaats
daarvan worden leraren beloond met meer werk, meer administratie, en meer kinderen in de klas.</p>
<p>Als wij straks zelf oud zijn, en we kunnen niet de aandacht geven die nodig is, wie is er dan nog goed genoeg opgeleid om ons te verzorgen?
Is het echt nodig dat we hierover twijfelen en ons zorgen maken? Niet als er een juiste en gelijkwaardige waardering voor het werk is.</p>
<p>Ik heb ontzettend veel respect voor de leraren die gestaakt hebben en acties opzetten om hier aandacht voor te vragen. Ja het is soms niet zo handig
als je kind thuis zit (geldt net zo goed voor ons), maar het is het waard. Het gaat over onze toekomst! De overheid en samenleving moet dit goed regelen, niet 1 malig maar
structureel. Ik hoop dat wij daarmee ook zorgeloos van onze oude dag kunnen genieten.</p>
<p>Dank,
Remko</p>
]]></content:encoded></item><item><title>Migrated to Linux</title><link>/posts/2019-09-10-migrated-to-linux/</link><pubDate>Tue, 10 Sep 2019 00:00:00 +0000</pubDate><guid>https://www.evilcoder.org/posts/2019-09-10-migrated-to-linux/</guid><description>&lt;p&gt;If you would have asked me a few years ago, whether I was going to migrate my servers to Linux?
I would have laughed and not even consider it. Since 2004 I have hosted all my own servers on the
FreeBSD OS. I had one CentOS machine, because OpenXchange on FreeBSD was not the best experience.
But now, in 2019, all my servers are running one of the Linux OS&amp;rsquo;es. Mainly Ubuntu.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you would have asked me a few years ago, whether I was going to migrate my servers to Linux?
I would have laughed and not even consider it. Since 2004 I have hosted all my own servers on the
FreeBSD OS. I had one CentOS machine, because OpenXchange on FreeBSD was not the best experience.
But now, in 2019, all my servers are running one of the Linux OS&rsquo;es. Mainly Ubuntu.</p>
<h2 id="how-did-we-get-there">How did we get there?</h2>
<p>Short summary: I did not feel at home anymore.</p>
<p>Larger summary: The creation of the Code of Conduct within FreeBSD made me frown a lot, and still does. It&rsquo;s
largely American oriented and does not take non-American stuff in consideration, or not enough.
The current leadership is more worried about personal social media posts and how to respond to
that then about guiding the project into the next phase. The world is not entirely American and
different people with different cultures were welcome within FreeBSD. My personal feeling is that
that is no longer the case.</p>
<p>I realise that if you read this, this might make you frown as well. I am a long standing community
member, which covers a large part of my adult life. Does all this outweigh my long-term connection
to the project? Yes.</p>
<p>Beyond the &ldquo;social&rdquo; side of the project, I also think that while being conservative, we missed the boat
on multiple occassions. Things come in late, or are not &ldquo;addressed&rdquo; at all. Take containers. They are
the current hype for microservices. There is no way to do something with that within FreeBSD. FreeBSD
has jails, which is a more heavy weight container-kind-of-solution. Or better said it is a more lightweight
virtual machine instead.</p>
<p>Tools that use containers, like Gitlab CI/CD and many other things make use of those services. FreeBSD
just does not have them. It&rsquo;s not sexy enough to run it in your DC. Sadly I do not see much activity
company wise in the Netherlands either that suggests that I am wrong. Most things that I do see in my
professional life are Linux related machines.</p>
<h2 id="is-this-the-end-for-me">Is this the end for me?</h2>
<p>With my current FreeBSD implementations, yes. All my machines are migrated to Linux, there are no
exceptions anymore. This makes it easier for my automation tooling, because everything runs on the
same foundation and files can be found on the same place. Same goes for packages etc.</p>
<p>Farewell FreeBSD, you have served me well and I think that I earned the right to use it by all my
contributions. I hope that a less politically minded core team stands up at some point and changes
the game. Perhaps that will make me rejoin the project that I once was so proud of.</p>
]]></content:encoded></item><item><title>In Memoriam Paul Schenkeveld 1963-2015</title><link>/posts/2019-11-24-im-paul-schenkeveld/</link><pubDate>Sun, 24 Feb 2019 23:09:32 +0000</pubDate><guid>https://www.evilcoder.org/posts/2019-11-24-im-paul-schenkeveld/</guid><description>&lt;p&gt;A few years ago, I was informed that Paul Schenkeveld had passed away. That
was very unpleasant news ofcourse. I knew Paul for some years, at the D-BUG
or NLUUG BSD days he was one of the organisers and I was one of the speakers
back then. In addition he was one of the main organisers of the 2011 EuroBSDCon in
Maarssen. I always saw Paul.. and then Cor.. or the other way around.&lt;/p&gt;</description><content:encoded><![CDATA[<p>A few years ago, I was informed that Paul Schenkeveld had passed away. That
was very unpleasant news ofcourse. I knew Paul for some years, at the D-BUG
or NLUUG BSD days he was one of the organisers and I was one of the speakers
back then. In addition he was one of the main organisers of the 2011 EuroBSDCon in
Maarssen. I always saw Paul.. and then Cor.. or the other way around.</p>
<p>So when I saw Cor at the NLUUG a few days ago.. I missed Paul ofcourse. I had
not seen Cor for a few years and not after Paul&rsquo;s passing.
Seeing Cor alone instantly reminded me of Paul. You are still missed Paul.
Rest in peace!</p>
<p>Image taken from db.net where both Paul (Left) and Cor (Right) appeared on
photo.</p>
<p>
  <img loading="lazy" src="http://www.db.net/gallery/BSDCan/BSDCan_2010_day_2/Images/6.jpg" alt="Paul and Cor"  title="Paul and Cor on BSDCan 2010"  /></p>
]]></content:encoded></item><item><title>Reponse zones in BIND (RPZ/Blocking unwanted traffic).</title><link>/2018/02/05/reponse-zones-in-bind-rpz-blocking-unwanted-traffic/</link><pubDate>Mon, 05 Feb 2018 23:09:32 +0000</pubDate><guid>https://www.evilcoder.org/2018/02/05/reponse-zones-in-bind-rpz-blocking-unwanted-traffic/</guid><description>&lt;p&gt;A while ago, my dear colleague Mattijs came with an interesting option in BIND. Response zones. One can create custom &amp;ldquo;zones&amp;rdquo; and enforce a policy on that.&lt;/p&gt;
&lt;p&gt;I never worked with it before, so I had no clue at all what to expect from it. Mattijs told me how to configure it (see below for an example) and offered to slave his RPZ policy-domains.&lt;/p&gt;
&lt;p&gt;All of a sudden I was no longer getting a lot of ADS/SPAM and other things. It was filtered. Wow!&lt;/p&gt;</description><content:encoded><![CDATA[<p>A while ago, my dear colleague Mattijs came with an interesting option in BIND. Response zones. One can create custom &ldquo;zones&rdquo; and enforce a policy on that.</p>
<p>I never worked with it before, so I had no clue at all what to expect from it. Mattijs told me how to configure it (see below for an example) and offered to slave his RPZ policy-domains.</p>
<p>All of a sudden I was no longer getting a lot of ADS/SPAM and other things. It was filtered. Wow!</p>
<p>His RPZ zones were custom made and based on PiHole, where PiHole adds hosts to the local &ldquo;hosts&rdquo; file and sends it to 127.0.0.1 (your local machine), which prevents it to reach the actual server at all, RPZ policies are much stronger and more dynamic.</p>
<p>RPZ policies offer the use of &ldquo;redirecting&rdquo; queries. What do I mean with that? well you can force a ADVERTISEMENT (AD for short) site / domain to the RPZ policy and return a NXDOMAIN. It no longer exists for the end-user. But you can also CNAME it to a domain/host you own and then add a webserver to that host and tell the user query&rsquo;ing the page: &ldquo;The site you are trying to reach had been pro-actively blocked by the DNS software. This is an automated action and an automated response. If you feel that this is not appropriate, please let us know on <mail link>&rdquo;, or something like that.</p>
<p>Once I noticed that and saw the value, I immediately saw the benefit for companies and most likely schools and home people. Mattijs had a busy time at work and I was recovering from health issues, so I had &ldquo;plenty&rdquo; of time to investigate and read on this. The RPZ policies where not updated a lot and caused some problems for my ereaders for example (msftcncsi.com was used by them, see another post on this website for being grumpy about that). And I wanted to learn more about it. So what did I do?</p>
<p>Yes, I wrote my own parser. In perl. I wrote a &ldquo;rpz-generator&rdquo; (its actually called like that). I added the sources Mattijs used and generated my own files. They are rather huge, since I blocked ads, malware, fraud, exploits, windows stuff and various other things (gambling, fakenews, and stuff like that).</p>
<p>I also included some whitelists, because msfctinc was added to the lists and it made my ereaders go beserk, and we play a few games here and there which uses some advertisement sites, so we wanted to exempt them as well. It&rsquo;s better to know which ones they are and selectively allow them, then having traffic to every data collector out there.</p>
<p>This works rather well. I do not get a lot of complaints that things are not working. I do see a lot of queries going to &ldquo;banned&rdquo; sites everyday. So it is doing something .The most obvious one is that search results on google, not always are clickable. The ones that have those [ADV] sites, are blocked because they are advertising google sponsored sites, and they are on the list.. and google-analytics etc. It doesn&rsquo;t cause much harm to our internet surfing or use experience, with the exception of the ADV sites I just mentioned. My wife sometimes wants to click on those because she searches for something that happends to be on that list, but apart from that we are doing just fine.</p>
<p>One thing though, I wrote my setup and this article with my setup using &ldquo;NXDOMAIN&rdquo; which just gives back &ldquo;site does not exist&rdquo; messages. I want to make my script more smart by making it a selectable, so that some categories are CNAMED to a filtering domain and webpage, and some are NXDOMAIN&rsquo;ed. If someone has experience with that, please show me some idea&rsquo;s and how that looks like and whether your end-users can do something with it or not. I think schools will be happy to present a block-page instead of NXdomain&rsquo;ing some sites 🙂</p>
<p>Acknowledgements: Mattijs for teaching and showing me RPZ, ISC for placing RPZ in NAMED, and zytrax.com for having such excellent documentation to RPZ. The perl developers for having such a great tool around, and the various sites I use to get the blocklists from. Thank you all!</p>
<p>If you want to know more about the tool, please contact me and we can share whatever information is available 🙂</p>
]]></content:encoded></item><item><title>FreeBSD: Using Open-Xchange on FreeBSD</title><link>/2017/08/29/freebsd-using-open-xchange-on-freebsd/</link><pubDate>Tue, 29 Aug 2017 07:48:17 +0000</pubDate><guid>https://www.evilcoder.org/2017/08/29/freebsd-using-open-xchange-on-freebsd/</guid><description>&lt;p&gt;If you go looking for a usable webmail application, then you might end up with Open-Xchange (OX for short). Some larger ISP&amp;rsquo;s are using OX as their webmail application for customers. It has a multitude of options available, using multiple email accounts, caldav/carddav included (not externally (yet?)) etc. There are commercial options available for these ISP&amp;rsquo;s, but also for smaller resellers etc.&lt;/p&gt;
&lt;p&gt;But, there is also the community edition available. Which is the installation you can run for free on your machine(s). It does not have some of the fancy modules that large setups need and require, and some updates might follow a bit later which are more directly delivered to paying customers, but it is very complete and usable.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you go looking for a usable webmail application, then you might end up with Open-Xchange (OX for short). Some larger ISP&rsquo;s are using OX as their webmail application for customers. It has a multitude of options available, using multiple email accounts, caldav/carddav included (not externally (yet?)) etc. There are commercial options available for these ISP&rsquo;s, but also for smaller resellers etc.</p>
<p>But, there is also the community edition available. Which is the installation you can run for free on your machine(s). It does not have some of the fancy modules that large setups need and require, and some updates might follow a bit later which are more directly delivered to paying customers, but it is very complete and usable.</p>
<p>I decided to setup this for my private clients who like to use a webmail client to access their email. At first I ran this on a VM using Bhyve on FreeBSD. The VM ran on CentOS6 and had the necessary bits installed for the OX setup (see: <a href="https://oxpedia.org/wiki/index.php?title=AppSuite:Open-Xchange_Installation_Guide_for_CentOS_6">https://oxpedia.org/wiki/index.php?title=AppSuite:Open-Xchange_Installation_Guide_for_CentOS_6</a>). I modified the files I needed to change to get this going, and there, it just worked. But, running on a VM, with ofcourse limited CPU and Memory power assigned (There is always a cap) and it being emulated, I was not very happy with it. I needed to maintain an additional installation and update it, while I have this perfectly fine FreeBSD server instead. (Note that I am not against using bhyve at all, it works very well, but I wanted to reduce my maintenance base a bit :-)).</p>
<p>So a few days ago I considered just moving the stuff over to the FreeBSD host instead. And actually it was rather trivial to do with the working setup on CentOS.</p>
<p>At this moment I do not see an easy way to get the source/components directly from within FreeBSD. I have asked OX for help on this, so that we can perhaps get this sorted out and perhaps even make a Port/pkg out of this for use with FreeBSD.</p>
<h2 id="the-required-host-changes-and-software-installation">The required host changes and software installation</h2>
<p>The first thing that I did was to create a zfs dataset for /opt. The software is normally installed there, and in this case I wanted to have a contained location which I can snapshot, delete, etc, without affecting much of the normal system. I copied over the /opt/open-xchange directory from my CentOS installation. I looked at the installation on CentOS and noticed that it used a specific user &lsquo;open-xchange&rsquo;, which I created on my FreeBSD host. I changed the files to be owned by this user. Getting a process listing on the CentOS machine also revealed that it needed Java/JDK. So I installed the openjdk8 pkg (&lsquo;&lsquo;pkg install openjdk8&rsquo;&rsquo;). The setup did not yet start, there were errors about /bin/bash missing. Obviously that required installing bash (&lsquo;&lsquo;pkg install bash&rsquo;&rsquo;) and you can go with two ways, you can alter every shebang (#!) to match /usr/local/bin/bash (or better yet #!/usr/bin/env bash), or you can symlink /usr/local/bin/bash to /bin/bash, which is what I did (I asked OX to make it more portable by using the env variant instead).</p>
<p>The /var/log/open-xchange directory does not normally exist, so I created that and made sure that &lsquo;&lsquo;open-xchange&rsquo;&rsquo; could write to that. (mkdir /var/log/open-xchange &amp;&amp; chown open-xchange /var/log/open-xchange).</p>
<p>I was able to startup the /opt/open-xchange/sbin/open-xchange process with that. I could not yet easily reach it, on the CentOS installation there are two files in the Apache configuration that needed some attention on my FreeBSD host. The Apache include files: ox.conf and proxy_http.conf will give away hints about what to change. In my case I needed to do the redirect on the Vhost that runs OX (RedirectMatch ^/$ /appsuite/) and make sure the /var/www/html/appsuite directory is copied over from the CentOS installation as well. You can stick it in any location, as long as you can reach it with your webuser and Alias it to the proper directory and setup directory access).</p>
<h2 id="apache-configuration-reverse-proxy-mode">Apache configuration (Reverse proxy mode)</h2>
<p>The proxy_http.conf file is more interesting, it includes the reverse proxy settings to be able to connect to the java instance of OX and service your clients. I needed to add a few modules in Apache so that it could work, I already had several proxy modules enabled for different reasons, so the list below can probably be trimmed a bit to the exact modules needed, but since this works for me, I might as well just show you;</p>
<p style="font-size:13px">
  LoadModule slotmem_shm_module libexec/apache24/mod_slotmem_shm.so<br />LoadModule deflate_module libexec/apache24/mod_deflate.so<br />LoadModule expires_module libexec/apache24/mod_expires.so<br />LoadModule proxy_module libexec/apache24/mod_proxy.so<br />LoadModule proxy_connect_module libexec/apache24/mod_proxy_connect.so<br />LoadModule proxy_http_module libexec/apache24/mod_proxy_http.so<br />LoadModule proxy_scgi_module libexec/apache24/mod_proxy_scgi.so<br />LoadModule proxy_wstunnel_module libexec/apache24/mod_proxy_wstunnel.so<br />LoadModule proxy_ajp_module libexec/apache24/mod_proxy_ajp.so<br />LoadModule proxy_balancer_module libexec/apache24/mod_proxy_balancer.so<br />LoadModule lbmethod_byrequests_module libexec/apache24/mod_lbmethod_byrequests.so<br />LoadModule lbmethod_bytraffic_module libexec/apache24/mod_lbmethod_bytraffic.so<br />LoadModule lbmethod_bybusyness_module libexec/apache24/mod_lbmethod_bybusyness.so
</p>
<p>After that it was running fine for me. My users can login to the application and the local directory&rsquo;s are being used instead of the VM which ran it first. If you notice previous documentation on this subject, you will notice that there are more third party packages needed at that time. It could easily be that there are more modules needed than that I wrote about. My setup was not clean, the host already runs several websites (one of them being this one) and ofcourse support packages were already installed.</p>
<p>Updating is currently <strong>NOT</strong> possible. The CentOS installation requires running &lsquo;&lsquo;yum update&rsquo;&rsquo; periodically, but that is obviously not possible on FreeBSD. The packages used within CentOS are not directly usable for FreeBSD. I have asked OX to provide the various Community base and optional modules as .tar.gz files (raw) so that we can fetch them and install them on the proper location(s). As long as the .js/.jar files etc are all there and the scripts are modified to start, it will just work. I have not (yet) created a startup script for this yet. For the moment I will just start the VM and see whether there are updates and copy them over instead. Since I did not need to do additional changing on the main host, it is a very easy and straight forward process in this case.</p>
<h2 id="support">Support</h2>
<p>There is no support for OX on FreeBSD. Ofcourse I would like to see at least some support to promote my favorite OS more, but that is a financial situation. It might not cost a lot to deliver the .tar.gz files so that we can package them and spread the usage of OX on more installations (and thus perhaps add revenue for OX as commercial installation), but it will cost FTE&rsquo;s to support more then that. If you see a commercial opportunity, please let them know so that this might be more and more realistic.</p>
<p>The documentation written above is just how I have setup the installation and I wanted to share it with you. I do not offer support on it, but ofcourse I am willing to answer questions you might have about the setup etc. I did not include the vhost configuration in it&rsquo;s entirely, if that is a popular request, I will add it to this post.</p>
<h2 id="open-questions-to-ox">Open Questions to OX</h2>
<p>So as mentioned I have questioned OX for some choices:</p>
<ul>
<li>Please use a more portable path for the Bash shell (#!/usr/bin/env bash)</li>
<li>Please allow the use of a different localbase (/usr/local/open-xchange for example)</li>
<li>Please allow FreeBSD packagers to fetch a &ldquo;clean&rdquo; .tar.gz, so that we can package this for OX and distribute it for our end-users.</li>
<li>Unrelated to the post above: Please allow the usage of external caldav/carddav providers</li>
</ul>
<p><strong>Edit:</strong></p>
<p>I have found another thing that I needed to change. I needed to use gsed (Gnu-sed) instead of FreeBSD-sed so that the listuser scripts work. Linux does that a bit differently but if you replace sed with gsed those scripts will work fine.</p>
<p>I have not yet got some feedback from OX.</p>
]]></content:encoded></item><item><title>The epic spam battle from SpamAssassin (10 + year user) to rspamd.</title><link>/2017/04/08/the-epic-spam-battle-from-spamassassin-10-year-user-to-rspamd/</link><pubDate>Sat, 08 Apr 2017 12:35:06 +0000</pubDate><guid>https://www.evilcoder.org/2017/04/08/the-epic-spam-battle-from-spamassassin-10-year-user-to-rspamd/</guid><description>&lt;p&gt;For many System Administrators that have public facing Mailservers, it is an ongoing battle.. SPAM. Since there is money to make, it will never ever go away, but we can try to mitigate this.&lt;/p&gt;
&lt;h2 id="introduction-on-my-usage-of-anti-spam-products"&gt;Introduction on my usage of anti-spam products&lt;/h2&gt;
&lt;p&gt;For many moons I have used the SpamAssassin product in various forms, simply as a client to check every email on delivery, as daemon where multiple servers check one instance, as part of MailScanner where a single (replicated) database was responsible for storing all bits and pieces combined with local additional rules. This worked fine for years, but, our external MX servers are not the most powerful machines in the world. We need to be selective on what we load on them. And the ever increasing spam battle just makes sure that your memory and processing power is going faster then the system(s) could continuously deliver.More rules, more Anti-Virus, more regular expressions, more downloading, parsing and re2c’ing files that gets harder and harder for the systems every time the amount of rules etc increases.&lt;/p&gt;</description><content:encoded><![CDATA[<p>For many System Administrators that have public facing Mailservers, it is an ongoing battle.. SPAM. Since there is money to make, it will never ever go away, but we can try to mitigate this.</p>
<h2 id="introduction-on-my-usage-of-anti-spam-products">Introduction on my usage of anti-spam products</h2>
<p>For many moons I have used the SpamAssassin product in various forms, simply as a client to check every email on delivery, as daemon where multiple servers check one instance, as part of MailScanner where a single (replicated) database was responsible for storing all bits and pieces combined with local additional rules. This worked fine for years, but, our external MX servers are not the most powerful machines in the world. We need to be selective on what we load on them. And the ever increasing spam battle just makes sure that your memory and processing power is going faster then the system(s) could continuously deliver.More rules, more Anti-Virus, more regular expressions, more downloading, parsing and re2c’ing files that gets harder and harder for the systems every time the amount of rules etc increases.</p>
<p>I already mentioned that this worked fine for years. I switched to MailScanner for our MX’es not too long ago, and I am happy with that, except that it takes additional load on the machines, and will only judge about mails when they are already in. I contributed to MailScanner and specifically to the MailWatch project for reasons of LDAP authentication and more of those things, where I found space to improve. Even though I like the system very much, it is not how I want to prevent Spam from coming in. It might be a good fit for you though, it offers a quarantine where users can selectively release emails and mark them as spam and such and you can generate emails that send the amount of potentially missed emails and a link to them etc. Some of our users where happy with that as well, and so was I.</p>
<h2 id="limitations-of-our-handling-of-email">Limitations of our handling of email</h2>
<p>But, resources were becoming a problem. Yes I can upgrade my external MX’es ofcourse and load them with more memory and CPU power, but that costs money. Money that is hard earned in the hosting world, because there is plenty to choose from, even if we give the best prices around, it still takes multiple additional customers to warrant the higher bills (that is not taking into account that profit would be fun for additional investments in the company so that our users can get even better products).</p>
<p>So, given the saturated market, I was not going to spend additional money on our machines just yet. Another thing is that I wanted to prevent spam from coming into the machine in the first place, so reject them at the border where possible, so I do not have to cater them. (See it as border patrol, it’s easier to prevent things coming in, then to handle them once they are in). I noticed that several email servers where already doing that when we forward mail for our domains to lets say gmail or other companies that people are happy to use. Those servers, like gmail, either rate limit you or they just deny the emails before you are able to send them. Leaving you with the problems instead of the gmail user itself. Magnificent. But how does that work? for Postfix, which I use that means using a milter, specifically in this case rmilter, which binds into the product on the SMTP level, checks signatures stored, scans the content and verifies with bayes and a neural network whether this is OK or not, and then either rejects it before processing it, greylisting it when it seems spammy or adds an header to the message and forwards it to it’s final destination. If we are the final destination, then the header is taken into account and the message is automatically put in the Spam folder, or for gmail/hotmail users this is the ‘unwanted email’ folder or whatever it is called nowadays. I have put filters in place, that learn your behaviour, so if a message is put in the Spam folder and is not spam and you move it back to for example the INBOX, then the system learns that it should not mark it as spam and try to do better next time.</p>
<h2 id="the-product-rspamd">The product: rspamd</h2>
<p>But what product delivers that ? After talking with a postmaster team member of <strong>FreeBSD</strong>, I found out about <strong>rspamd</strong>, and that the author is a fellow-FreeBSD-committer as well. I implemented it (it took some time to learn the curve, but essentially it is rather easy, try it!). It has less load then the various spam assassin products and additional applications that support it (like mailscanner and mailwatch), it does not need a webserver by itself etc. So it reduced my memory footprint with around 400mb’s continuously of less memory usage. That is a whole lot of you have mb’s to spare instead of handing them out.</p>
<h2 id="how-does-it-globally-work">How does it globally work?</h2>
<p>I also configured rspamd to behave like the following;</p>
<ul>
<li>
<p>Both our external MX’es have a local bayes-classifier and various other local databases. I used the suggested three database tier on the machine and I extended both machines to use stunnel to contact eachother over the stunnel to the remote database. I changed all configuration options to not only use “servers = “localhost”;“ but instead “servers = “localhost,localhost:26379”; and spreading that across every redis line I could find. I then restarted rspamd on both machines and noticed that there is a lot of things going on, it seems that everything is written and read on both machines. Using the webinterface, you’ll sometime get errors, not sure why that is, and history is not always consistent. but it’s for management purposes only so not very problematic in this case. Both MX’es are checking on their localhost, and “also_check” the remote machine over an internal private network that I have setup.</p>
</li>
<li>
<p>Our internal machines that handle the delivery of the email, use both MX’es as rspamd instance as configured in rmilter. They do not handle anything themselves, except for Virus Scanning (which is also done on the MX but as well on the local machine, but only for email not received from the MX’es, like outgoing email). That means less overhead for those machines and only using the two machines where we know they are working. I also extended these machines to use redis on the MX’es instead of locally and configured them both in the configuration, again using stunnel. rmilter uses the redis databases to store and save messages that we have send and get replies and such. In the future if rspamd is by itself capable of handling this, rmilter will be taken out and only rspamd will run like mentioned.</p>
</li>
</ul>
<h2 id="learning-spamham-messages">Learning spam/ham messages</h2>
<p>For now this seems to work very well, I have implemented a dovecot script that triggers when someone moves a message from spam to inbox (‘learn-ham.sh’) and from inbox or other mailboxes to the spambox (‘learn-spam.sh’).</p>
<p>The contents of the files look like the follwing, where learn_spam and learn_ham are in the appropriate places ofcourse.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>&gt; <span style="color:#75715e">#!/bin/sh</span>
</span></span><span style="display:flex;"><span>&gt;
</span></span><span style="display:flex;"><span>&gt; data<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>cat<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>&gt;
</span></span><span style="display:flex;"><span>&gt; echo &amp;<span style="color:#75715e">#8220;$data&amp;#8221; | /usr/local/bin/rspamc -h MX1 -P &lt;secret password for MX1&gt; learn_spam</span>
</span></span><span style="display:flex;"><span>&gt;
</span></span><span style="display:flex;"><span>&gt; echo &amp;<span style="color:#75715e">#8220;$data&amp;#8221; | /usr/local/bin/rspamc -h MX2 -P &lt;secret password for MX2&gt; learn_spam</span>
</span></span></code></pre></div><p>Ofcourse it takes additional understanding of how emails work, how your environment works and what is acceptable or not. On the course of just a few days we processed more then 10k of emails (yes there are many providers doing more emails, everyone has it’s own perks ;-)). and we have learned more then 60 emails in just a day after enabling users to do their own training.</p>
<h2 id="one-note">One note</h2>
<p>A little note about the rejecting of spam, we only reject spam when the message is really spammy and cannot be easily something else. Most emails that I saw so far are forwarded with an additional header instead of being rejected and the emails that are rejected are really spam. Users will never ever see them, which is good enough for my environment but might be something different for your environment. Please dry-run it at first to see how it matches your environment.</p>
<h2 id="references">References</h2>
<p>The script for learning spam under dovecot comes from: <a href="https://kaworu.ch/blog/2014/03/25/dovecot-antispam-with-rspamd/">https://kaworu.ch/blog/2014/03/25/dovecot-antispam-with-rspamd/</a> user Alex.</p>
<p>The documentation I used for rspamd comes from <a href="https://www.rspamd.com">https://www.rspamd.com</a> itself.</p>
<p>The sieve filters that I use for dovecot are from Dovecot itself <a href="https://wiki2.dovecot.org/HowTo/AntispamWithSieve">https://wiki2.dovecot.org/HowTo/AntispamWithSieve</a></p>
<p>Custom blacklisting of domains and such come from: <a href="https://gist.github.com/kvaps/25507a87dc287e6a620e1eec2d60ebc1">https://gist.github.com/kvaps/25507a87dc287e6a620e1eec2d60ebc1</a></p>
]]></content:encoded></item><item><title>FreeBSD Dutch Documentation Project</title><link>/2017/03/22/freebsd-dutch-documentation-project/</link><pubDate>Wed, 22 Mar 2017 14:01:11 +0000</pubDate><guid>https://www.evilcoder.org/2017/03/22/freebsd-dutch-documentation-project/</guid><description>&lt;p&gt;So. It had been a while before I had proper time to look into the Dutch translation efforts again.&lt;/p&gt;
&lt;h2 id="history"&gt;History&lt;/h2&gt;
&lt;p&gt;Due to various reasons not discussed here, I was not able to see to a proper translation. Rene did a lot of work (thank you for that Rene!).&lt;/p&gt;
&lt;h2 id="the-po-system"&gt;The PO system&lt;/h2&gt;
&lt;p&gt;First of all, i am going to discuss a bit about the PO system, which is a gettext way of doing translations. It chops texts into msgstr’s (message strings) and then translates those strings using msgid’s. Same lines are translated the same, this might be a good option, unless the context changed between the lines and then you might get ‘google translate’ kind of ways.&lt;/p&gt;</description><content:encoded><![CDATA[<p>So. It had been a while before I had proper time to look into the Dutch translation efforts again.</p>
<h2 id="history">History</h2>
<p>Due to various reasons not discussed here, I was not able to see to a proper translation. Rene did a lot of work (thank you for that Rene!).</p>
<h2 id="the-po-system">The PO system</h2>
<p>First of all, i am going to discuss a bit about the PO system, which is a gettext way of doing translations. It chops texts into msgstr’s (message strings) and then translates those strings using msgid’s. Same lines are translated the same, this might be a good option, unless the context changed between the lines and then you might get ‘google translate’ kind of ways.</p>
<p>Back to the story…</p>
<p>After getting time again to see this through I noticed that we started using the “PO” system, using gettext. Our handbook (for example) is now translated into one huge book.xml file which is then cut into msgstr’s that can be translated to msgid’s. For this I use the poedit application (the PRO version) so that I have counters and translation suggestions from the online Translation Memory(TM) that we all develop. I also contribute the FreeBSD translations back to the TM so that everyone can profit from it.</p>
<p>I am now first synchronising the Glossary because that didn’t change much with the current online translation and working my way back to what had been translated already and translating the missing bits and pieces in between. Mike (co worker at Snow) also did a tremendous job in getting this into better shape the last year which had not yet been merged back to the online variants because it was not yet complete. I can use that information though to generate a manual handbook variant of that version and use that to even further use the current translation effort into the gettext/po system.</p>
<h2 id="biting-the-bullet">Biting the bullet</h2>
<p>As one of the first translation teams to use this, I expect to hit some rocks on the road. For example, there are lines that do not need translation, mailing list names are the same in every language, perhaps the description changes but not the ‘realnames’. Same goes for my entity (&amp;a.remko) which does not change, nor my PGPkey. And if those things change, they require changing over all translation efforts as well as the original english version. We are looking into a way to ‘ignore’ them for the po system but include them when building. So that pgpkeys and such are always up to date.</p>
<p>I also had been discussing this with Vaclav the developer of poedit, and he mentioned that it does not matter much, because when a line changes and you update the po, those lines will be invalidated and need ‘retranslation’ for the entire string. So that all gets us in interesting situations that we did not encounter before. I am biting the bullet myself after we have discussed this a few years ago and I hope that the entire project can benefit from that.</p>
<h2 id="alternative-options-pre-translate-merge-current-translations-automatically">Alternative options, pre-translate, merge current translations automatically?</h2>
<p>And yes, a valid question would be, cannot you merge the current translated information into the po system automatically. If every word was on the exact same spot and line, yes this might be an option. Sadly because of grammer and different wording (longer/shorter) this changes rapidly from line 1 already and is thus not easily done. If you have suggestions however, we are always willing to listen. Please join us on <a href="mailto:translators@FreeBSD.org">translators@FreeBSD.org</a> so that we can discuss those things better :-).</p>
]]></content:encoded></item><item><title>Kobo readers using the internet</title><link>/2017/03/22/kobo-readers-using-the-internet/</link><pubDate>Wed, 22 Mar 2017 13:13:49 +0000</pubDate><guid>https://www.evilcoder.org/2017/03/22/kobo-readers-using-the-internet/</guid><description>&lt;p&gt;So I have this situation, where I couldn’t get my kobo reader to connect to the internet and fetch updates and/or use kobo+ for example.&lt;/p&gt;
&lt;p&gt;I started debugging with Ubiquiti ages ago to see where the problem lies. In the meantime I was unable to continue with this, but I had an interesting thought yesterday. I sniffed the traffic from the hardware (mac) address of the ereader and noticed that it tried to resolve: &lt;a href="http://www.msftncsi.com"&gt;http://www.msftncsi.com&lt;/a&gt; and fetch /ncsi.txt. The site is a microsoft network connection information page that informs microsoft systems whether or not an active internet connection is seen.&lt;/p&gt;</description><content:encoded><![CDATA[<p>So I have this situation, where I couldn’t get my kobo reader to connect to the internet and fetch updates and/or use kobo+ for example.</p>
<p>I started debugging with Ubiquiti ages ago to see where the problem lies. In the meantime I was unable to continue with this, but I had an interesting thought yesterday. I sniffed the traffic from the hardware (mac) address of the ereader and noticed that it tried to resolve: <a href="http://www.msftncsi.com">http://www.msftncsi.com</a> and fetch /ncsi.txt. The site is a microsoft network connection information page that informs microsoft systems whether or not an active internet connection is seen.</p>
<p>Somehow it seems that Kobo is also using that for it’s android based readers as well. Without it, the network connection just disconnects and does nothing. That is somewhat upsetting because the device is just perfectly able to connect to the network(s) and has relative free internet access. One thing is that I filter on DNS responses and exclude known malware/spam hosts and analytics sites like google. This reduces the amount of advertorials on the internet and bogus trackers. It seems that msftncsi.com is also on that list and thus gets an NXDOMAIN when querying for it.</p>
<p>I do not entirely understand why an ereader would need this kind of information before being able to connect to the internet. The device should associate with a WiFi access point and get an address and the like. Whether or not that gives continued access to the internet is something that is a next step. So instead of giving up, it could just mark the WiFi symbol with an exclamation mark (!) to report that something might not work and/or just try to connect to the kobo internet environment. That would be more common use of the internet then depending on an internet file which might be blocked (such as in my case).</p>
<p>For now I changed my caching mikrotik’s to include msftncsi.com as a static entry and point that to my webserver and service the file instead. That makes sure the Kobo can connect to the environment and gives me full access over that file instead of some bogus remote site that might do nasty things (without me knowing).</p>
<p>Ofcourse I asked (nice and polite) Kobo to change this interesting behaviour.</p>
]]></content:encoded></item><item><title>Happy 2017!</title><link>/2017/01/04/happy-2017/</link><pubDate>Wed, 04 Jan 2017 19:59:39 +0000</pubDate><guid>https://www.evilcoder.org/2017/01/04/happy-2017/</guid><description>&lt;p&gt;After ‘relaunching’ my Blog I have been occupied with other activities. So I just took a little time to say “Happy 2017” to all of you. Perhaps there will be more entries this upcoming year.. 🙂&lt;/p&gt;</description><content:encoded>&lt;p>After ‘relaunching’ my Blog I have been occupied with other activities. So I just took a little time to say “Happy 2017” to all of you. Perhaps there will be more entries this upcoming year.. 🙂&lt;/p>
</content:encoded></item><item><title>Reorganised and back online</title><link>/2016/11/22/reorganised-and-back-online/</link><pubDate>Tue, 22 Nov 2016 12:45:12 +0000</pubDate><guid>https://www.evilcoder.org/2016/11/22/reorganised-and-back-online/</guid><description>&lt;p&gt;It took a gentle while to get the blog back up and running. I first considered
cleaning out the original blog, but that would have taken a lot of time and
effort. So instead I just vaporised the old blog (well, not really, but the
interwebs can no longer access it), and decided to rebuild the website. Please
feel welcome here, if I feel up for it, I might convert a few older blog
entries from the old blog to this new one. Do not expect periodic updates,
they will not happen probably.&lt;/p&gt;</description><content:encoded>&lt;p>It took a gentle while to get the blog back up and running. I first considered
cleaning out the original blog, but that would have taken a lot of time and
effort. So instead I just vaporised the old blog (well, not really, but the
interwebs can no longer access it), and decided to rebuild the website. Please
feel welcome here, if I feel up for it, I might convert a few older blog
entries from the old blog to this new one. Do not expect periodic updates,
they will not happen probably.&lt;/p>
</content:encoded></item><item><title>About my blogs</title><link>/2003/01/09/about-my-blogs/</link><pubDate>Thu, 09 Jan 2003 10:35:09 +0000</pubDate><guid>https://www.evilcoder.org/2003/01/09/about-my-blogs/</guid><description>&lt;p&gt;Back in 2003 I wrote the first bits of the site that you are visiting now. As an homage to that time I added a screenshot from the internet archive for future reference.
This will be back at some point.&lt;/p&gt;</description><content:encoded>&lt;p>Back in 2003 I wrote the first bits of the site that you are visiting now. As an homage to that time I added a screenshot from the internet archive for future reference.
This will be back at some point.&lt;/p>
</content:encoded></item></channel></rss>