<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>devops Archives - Tailored Cloud</title>
	<atom:link href="https://tailored.cloud/category/devops/feed/" rel="self" type="application/rss+xml" />
	<link>https://tailored.cloud/category/devops/</link>
	<description>Kubernetes, devops and everything cloud</description>
	<lastBuildDate>Thu, 18 Jul 2024 17:16:02 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6</generator>
	<item>
		<title>How to Get a Fully Functional, GitOps Driven Kubernetes Cluster for Free</title>
		<link>https://tailored.cloud/devops/gitops-driven-kubernetes-cluster-for-free/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=gitops-driven-kubernetes-cluster-for-free</link>
					<comments>https://tailored.cloud/devops/gitops-driven-kubernetes-cluster-for-free/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Thu, 18 Jul 2024 17:02:38 +0000</pubDate>
				<category><![CDATA[devops]]></category>
		<category><![CDATA[flux]]></category>
		<category><![CDATA[kubernetes]]></category>
		<guid isPermaLink="false">https://tailored.cloud/?p=489</guid>

					<description><![CDATA[<p>For the last few years, I&#8217;ve been working with my team at Giant Swarm on GitOps setup for our Kubernetes <a class="more-link" href="https://tailored.cloud/devops/gitops-driven-kubernetes-cluster-for-free/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/devops/gitops-driven-kubernetes-cluster-for-free/">How to Get a Fully Functional, GitOps Driven Kubernetes Cluster for Free</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>For the last few years, I&#8217;ve been working with my team at Giant Swarm on GitOps setup for our Kubernetes clusters (well, amongst other things). Recently, I&#8217;ve been more vocal about what we do and how do that &#8211; you can check my video from the <a href="https://www.youtube.com/watch?v=i4SsVIB_VkQ">Cloud Native Rejekts EU &#8217;24</a> presentation and an <a href="https://www.youtube.com/watch?v=_WGLpoxI0pg">episode I did recently with Johannes Koch</a> on his YouTube channel.</p>



<p>For some time now, I&#8217;m also doing a project that anyone can use to create your own Kubernetes cluster, that is entirely driven by the <a href="https://fluxcd.io/">Flux project</a>. What&#8217;s probably the best part of it, by using a free layer infrastructure from Oracle Cloud, you can run this cluster completely for free! So this gives you a fully functional Kubernetes cluster, managed by a great GitOps tool, and without paying a penny. Check the repo here: <a href="https://github.com/piontec/free-oci-kubernetes/">https://github.com/piontec/free-oci-kubernetes/</a></p>
<p>The post <a href="https://tailored.cloud/devops/gitops-driven-kubernetes-cluster-for-free/">How to Get a Fully Functional, GitOps Driven Kubernetes Cluster for Free</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/devops/gitops-driven-kubernetes-cluster-for-free/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cloud-native observability series: season finale available now!</title>
		<link>https://tailored.cloud/devops/cloud-native-observability-series-season-finale-available-now/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=cloud-native-observability-series-season-finale-available-now</link>
					<comments>https://tailored.cloud/devops/cloud-native-observability-series-season-finale-available-now/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Wed, 03 Feb 2021 19:03:46 +0000</pubDate>
				<category><![CDATA[devops]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=447</guid>

					<description><![CDATA[<p>For a long time now I&#8217;m writing more on the company blog than here. If you&#8217;re interested in the observability <a class="more-link" href="https://tailored.cloud/devops/cloud-native-observability-series-season-finale-available-now/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/devops/cloud-native-observability-series-season-finale-available-now/">Cloud-native observability series: season finale available now!</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>For a long time now I&#8217;m writing more on the company blog than here. If you&#8217;re interested in the observability series I was writing on Giant Swarm&#8217;s blog, the final episode of the series is <a href="https://www.giantswarm.io/blog/part-7-the-observability-finale">available</a>. Try to check the whole thing <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>
<p>The post <a href="https://tailored.cloud/devops/cloud-native-observability-series-season-finale-available-now/">Cloud-native observability series: season finale available now!</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/devops/cloud-native-observability-series-season-finale-available-now/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building applications for the cloud-native stack &#8211; continuation.</title>
		<link>https://tailored.cloud/dev/building-applications-for-the-cloud-native-stack-continuation/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=building-applications-for-the-cloud-native-stack-continuation</link>
					<comments>https://tailored.cloud/dev/building-applications-for-the-cloud-native-stack-continuation/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Fri, 05 Jun 2020 15:41:38 +0000</pubDate>
				<category><![CDATA[dev]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[cloud-native]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=444</guid>

					<description><![CDATA[<p>The next parts of my blog series I wrote for the company blog are available. Here&#8217;s part 2 and part <a class="more-link" href="https://tailored.cloud/dev/building-applications-for-the-cloud-native-stack-continuation/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/dev/building-applications-for-the-cloud-native-stack-continuation/">Building applications for the cloud-native stack &#8211; continuation.</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>The next parts of my blog series I wrote for the company blog are available. Here&#8217;s <a rel="noreferrer noopener" href="https://www.giantswarm.io/blog/a-few-dozen-lines-of-code-part-2-creating-the-application" target="_blank">part 2</a> and <a rel="noreferrer noopener" href="https://www.giantswarm.io/blog/part-3-deploying-the-application-with-helm" target="_blank">part 3</a>. I posted about starting the series and <a href="http://localhost:8080/kubernetes/schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series/">part 1</a> before.</p>



<p>In part 2, I&#8217;m discussing how to build a simple micro services app that will be used as a &#8216;lab rat&#8217; for our experiments with the cloud-native stack later.</p>



<p>In part 3, I&#8217;m describing how you can deploy your application (just a binary) as a full fledged Kubernetes concept, up to the point where we can pack everything into a Helm chart.</p>



<p>Enjoy!</p>
<p>The post <a href="https://tailored.cloud/dev/building-applications-for-the-cloud-native-stack-continuation/">Building applications for the cloud-native stack &#8211; continuation.</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/dev/building-applications-for-the-cloud-native-stack-continuation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Schrödinger&#8217;s come back to blogging: using the cloud-native stack series.</title>
		<link>https://tailored.cloud/kubernetes/schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series</link>
					<comments>https://tailored.cloud/kubernetes/schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Sat, 04 Apr 2020 12:13:32 +0000</pubDate>
				<category><![CDATA[dev]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[cloud-native]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=439</guid>

					<description><![CDATA[<p>It is a year since a wrote a blog post here. A lot has changed over this year in my <a class="more-link" href="https://tailored.cloud/kubernetes/schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/kubernetes/schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series/">Schrödinger&#8217;s come back to blogging: using the cloud-native stack series.</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="682" src="https://localhost:8080/wp-content/uploads/2020/04/code-820275_1280-1024x682.jpg" alt="" class="wp-image-440" srcset="https://tailored.cloud/wp-content/uploads/2020/04/code-820275_1280-1024x682.jpg 1024w, https://tailored.cloud/wp-content/uploads/2020/04/code-820275_1280-300x200.jpg 300w, https://tailored.cloud/wp-content/uploads/2020/04/code-820275_1280-768x512.jpg 768w, https://tailored.cloud/wp-content/uploads/2020/04/code-820275_1280.jpg 1280w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption>A Few Dozen Lines of Code&#8230; just don&#8217;t count YAML as code <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></figcaption></figure>



<p>It is a year since a <a href="http://localhost:8080/kubernetes/smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services/">wrote a blog post here</a>. A lot has changed over this year in my professional life. After quitting my previous job, I was looking for new opportunities and eventually decided to join the cool team at <a href="https://www.giantswarm.io/">Giant Swarm</a> as a platform architect. I have to say this was a great choice! My focus is now on enabling our customers to run applications on top of Kubernetes clusters.</p>



<p>Still, I didn&#8217;t want to stop blogging. Fortunately, I can now connect everything as part of my new job! So, I&#8217;m switching my focus slightly to cloud-native applications running on Kubernetes than just Kubernetes. This is also the topic of a blog series called &#8220;A Few Dozen Lines of Code&#8221;, the first part of which was <a href="https://blog.giantswarm.io/a-few-dozen-lines-of-code/">just published</a> on our company blog!</p>



<p>This series is meant to show you how using an opinionated cloud-native stack of tools installed on your Kubernetes cluster can make your dev and operations team happier than ever <img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Stay tuned for next parts, they should come shortly.</p>



<p>So, this entry is kind of &#8220;Schrödinger&#8217;s entry&#8221;. It is a new entry and it is not &#8211; as I&#8217;m just pointing you to <a href="https://blog.giantswarm.io/a-few-dozen-lines-of-code/">my article on the company blog</a>. Still, I will inform you if anything written by me is published there. Meanwhile, you can check other engineering articles published there, it&#8217;s good content.</p>
<p>The post <a href="https://tailored.cloud/kubernetes/schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series/">Schrödinger&#8217;s come back to blogging: using the cloud-native stack series.</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/kubernetes/schrodingers-come-back-to-blogging-using-the-cloud-native-stack-series/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SmartNat &#8211; dirt cheap Kubernetes ingress controller for TCP/UDP services</title>
		<link>https://tailored.cloud/kubernetes/smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services</link>
					<comments>https://tailored.cloud/kubernetes/smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services/#comments</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Wed, 03 Apr 2019 19:20:09 +0000</pubDate>
				<category><![CDATA[devops]]></category>
		<category><![CDATA[kubernetes]]></category>
		<category><![CDATA[controllers]]></category>
		<category><![CDATA[networking]]></category>
		<category><![CDATA[operators]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=420</guid>

					<description><![CDATA[<p>TL;DR SmartNat is a Kubernetes ingress controller for exposing a massive number of TCP/UDP services to the outside world using <a class="more-link" href="https://tailored.cloud/kubernetes/smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/kubernetes/smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services/">SmartNat &#8211; dirt cheap Kubernetes ingress controller for TCP/UDP services</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">TL;DR</h2>



<ul class="wp-block-list"><li>SmartNat is a Kubernetes ingress controller for exposing a massive number of TCP/UDP services to the outside world using just 1 server</li><li>available on GitHub: <a href="https://github.com/DevFactory/smartnat">https://github.com/DevFactory/smartnat</a></li></ul>



<h2 class="wp-block-heading">SmartNat &#8211; Kubernetes ingress controller for TCP/UDP services</h2>



<p>Some time ago I wrote posts about <a href="http://localhost:8080/kubernetes/simple-custom-kubernetes-controller/">writing a very simple Kubernetes controller</a> and <a href="http://localhost:8080/kubernetes/write-a-kubernetes-controller-operator-sdk/">using operator framework</a> to create a more complete one. Well, at the same time, I was starting to work on one at the company I&#8217;m working for. Meanwhile, I convinced management at the company to release the project as an OpenSource &#8211; and here it is!</p>



<p>The project is called SmartNat. It&#8217;s a Kubernetes ingress controller for TCP/UDP services that allows you to drive external traffic to your Services. It&#8217;s kind of Service with NodePort, but on strong steroids. It runs on a separate instance (well, you can run however you like, but this makes the most sense) and interconnects external (usually public) network with the subnet used by your Kubernetes cluster. SmartNat allows you to use multiple network interfaces, each one having multiple IP addresses to forward traffic from an external network to your services on a port-by-port basis. That way using just a single server or instance you can easily expose hundreds or even thousands of Services. The important property is that all of this is done using L3/L4 tools only, so SmartNat helps where HTTP based Ingresses can&#8217;t. Additionally, SmartNat supports simple traffic filtering of traffic coming from external subnet and also HA mode.</p>



<p>If you&#8217;re interested, check the project on github: <a href="https://github.com/DevFactory/smartnat">https://github.com/DevFactory/smartnat</a>.</p>
<p>The post <a href="https://tailored.cloud/kubernetes/smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services/">SmartNat &#8211; dirt cheap Kubernetes ingress controller for TCP/UDP services</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/kubernetes/smartnat-dirt-cheap-kubernetes-ingress-controller-for-tcp-udp-services/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Cache your Terraform providers to save space and time</title>
		<link>https://tailored.cloud/devops/cache-terraform-providers/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=cache-terraform-providers</link>
					<comments>https://tailored.cloud/devops/cache-terraform-providers/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Tue, 03 Jul 2018 20:41:14 +0000</pubDate>
				<category><![CDATA[devops]]></category>
		<category><![CDATA[terraform]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=320</guid>

					<description><![CDATA[<p>TL;DR: Set &#8220;TF_PLUGIN_CACHE_DIR&#8221;  environment variable to an empty dir, then rerun &#8220;terraform init&#8221; to switch to a shared providers directory. This <a class="more-link" href="https://tailored.cloud/devops/cache-terraform-providers/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/devops/cache-terraform-providers/">Cache your Terraform providers to save space and time</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2><img decoding="async" class="alignleft wp-image-311 size-thumbnail" title="Terraform Logo" src="https://localhost:8080/wp-content/uploads/2018/04/tf-150x150.png" alt="Terraform Logo" width="150" height="150" srcset="https://tailored.cloud/wp-content/uploads/2018/04/tf-150x150.png 150w, https://tailored.cloud/wp-content/uploads/2018/04/tf-300x300.png 300w, https://tailored.cloud/wp-content/uploads/2018/04/tf.png 400w" sizes="(max-width: 150px) 100vw, 150px" />TL;DR:</h2>
<ul>
<li>Set &#8220;TF_PLUGIN_CACHE_DIR&#8221;  environment variable to an empty dir, then rerun &#8220;terraform init&#8221; to switch to a shared providers directory. This allows to cache Terraform providers.</li>
</ul>
<h2>Dynamic Terraform providers</h2>
<p>Have you noticed, that when you run &#8220;terraform init&#8221;, Terraform fetches additional binaries from the Internet? These binaries are called &#8220;dynamic providers&#8221; and they are (mainly) interfaces to the infrastructure operators you&#8217;re using in your Terraform code.</p>
<p>So, for example, when you&#8217;re using AWS as an infrastructure provider, and you also use some templating to <a href="http://localhost:8080/devops/terraform_confg_files_changed_resources/">generate config files</a>, you might run &#8220;terraform version&#8221; and see something like this:</p>
<pre class="brush: plain; title: ; notranslate">
$ terraform version
Terraform v0.11.7
+ provider.aws v1.20.0
+ provider.null v1.0.0
+ provider.template v1.0.0
</pre>
<p>In the example above, we have 3 providers being used: the &#8220;null&#8221; and &#8220;template&#8221; provider and a specialized provider for talking with AWS API. You can find a list of projects developing these providers on <a href="https://github.com/terraform-providers">Github</a>. But where does Terraform keep these additional provider binaries? Well, each directory that is used as a root of Terraform project (where you run &#8220;terraform init&#8221;) has a hidden &#8220;.terraform&#8221; directory. There you can find the downloaded providers:</p>
<pre class="brush: bash; title: ; notranslate">
ll -h .terraform/plugins/linux_amd64
-rwxrwxr-x 1 piontec piontec 239 May 24 06:28 lock.json
-rwxr-xr-x 1 piontec piontec 71M May 24 06:28 terraform-provider-aws_v1.20.0_x4
-rwxr-xr-x 1 piontec piontec 12M Apr 19 07:29 terraform-provider-null_v1.0.0_x4
-rwxr-xr-x 1 piontec piontec 12M Apr 19 07:29 terraform-provider-template_v1.0.0_x4
</pre>
<p>This approach is nice, as all your projects are independent and self-contained: you can copy the whole directory to some other place or even machine and everything will still work. However, this approach has also a drawback. If you have multiple Terraform working directories, you have to download and store your providers for every one of them. As you can see, the basic set of providers listed above takes around 100MB &#8211; not much for today standards, but still. This gets even worse when you have multiple Terraform projects used by a group of people.  And when every one of them runs &#8220;terraform init&#8221;, he or she needs to wait for the binaries to be downloaded.</p>
<h2>A cache directory for providers to the rescue</h2>
<p>The solution to the above problem is actually very simple and already included in Terraform, although a little bit hard to find. You can create a single directory for storing your dynamic providers&#8217; binaries, then set the environment variable &#8220;TF_PLUGIN_CACHE_DIR&#8221; and you&#8217;re done! All Terraform commands run when this variable is set will use a shared directory to lookup and download dynamic providers. That way you can share them across projects and users, if only they have sufficient access permissions to that directory.</p>
<p>Let&#8217;s see how it works.</p>
<pre class="brush: bash; title: ; notranslate">
$ rm -rf .terraform
$ export TF_PLUGIN_CACHE_DIR=/opt/terraform-plugin-dir
$ terraform init -upgrade=true
$ ll -h /opt/terraform-plugin-dir/linux_amd64
-rwxr-xr-x 1 piontec piontec 71M May 24 06:28 terraform-provider-aws_v1.20.0_x4
-rwxr-xr-x 1 piontec piontec 12M Apr 19 07:29 terraform-provider-null_v1.0.0_x4
-rwxr-xr-x 1 piontec piontec 12M Apr 19 07:29 terraform-provider-template_v1.0.0_x4
$ ll .terraform/plugins/linux_amd64
-rwxrwxr-x 1 piontec piontec 239 May 24 06:28 lock.json
lrwxrwxrwx 1 piontec piontec 71 May 24 06:28 terraform-provider-aws_v1.20.0_x4 -&amp;amp;gt; /opt/terraform-plugin-dir/linux_amd64/terraform-provider-aws_v1.20.0_x4
lrwxrwxrwx 1 piontec piontec 71 May 24 06:28 terraform-provider-null_v1.0.0_x4 -&amp;amp;gt; /opt/terraform-plugin-dir/linux_amd64/terraform-provider-null_v1.0.0_x4
lrwxrwxrwx 1 piontec piontec 75 May 24 06:28 terraform-provider-template_v1.0.0_x4 -&amp;amp;gt; /opt/terraform-plugin-dir/linux_amd64/terraform-provider-template_v1.0.0_x4
$ du -hs .terraform
336K .terraform
</pre>
<p>So, after setting the &#8220;TF_PLUGIN_CACHE_DIR&#8221; variable, Terraform downloads providers to that directory and just links from your project&#8217;s &#8220;.terraform&#8221; directory to the shared directory. And that&#8217;s it! Now you can have just a single copy of each provider for the given provider version.</p>
<p>Please keep in mind, that each project still selects the providers&#8217; versions on its own: the fact that one Terraform directory downloaded a new provider version to the cache directory doesn&#8217;t mean that all other projects will use the new version as well. So, normal <a href="https://www.terraform.io/docs/configuration/providers.html#provider-versions">versioning restrictions</a> apply. If you want to be sure which version is locked for use with your current project, you can inspect SHA256 of files saved in one of the files in the &#8220;.terraform&#8221; directory:</p>
<pre class="brush: bash; title: ; notranslate">
$ cat .terraform/plugins/linux_amd64/lock.json
{
&quot;aws&quot;: &quot;3c78df7e116ed60bf917e4cd5cda5999ada163ebffd9142ea41aa7992252cda8&quot;,
&quot;null&quot;: &quot;d45c0f02cbc08b3915e143434e575298d4c638a2c93598c12f4ea551cd821abd&quot;,
&quot;template&quot;: &quot;f1d8e373d9f89d21fade8858a562bed75b463c814a2cf8fb750eb017083f1e88&quot;
}

$ ll /opt/terraform-plugin-dir/linux_amd64/
-rwxr-xr-x 1 piontec piontec 72686016 Apr 19 07:29 terraform-provider-aws_v1.15.0_x4
-rwxr-xr-x 1 piontec piontec 73263584 May 7 08:52 terraform-provider-aws_v1.17.0_x4
-rwxr-xr-x 1 piontec piontec 74119744 May 21 14:05 terraform-provider-aws_v1.19.0_x4
-rwxr-xr-x 1 piontec piontec 74242624 May 24 06:28 terraform-provider-aws_v1.20.0_x4
-rwxr-xr-x 1 piontec piontec 11621440 Apr 19 07:29 terraform-provider-null_v1.0.0_x4
-rwxr-xr-x 1 piontec piontec 11711744 Apr 19 07:29 terraform-provider-template_v1.0.0_x4

$ sha256sum /opt/terraform-plugin-dir/linux_amd64/terraform-provider-aws_v1.20.0_x4
3c78df7e116ed60bf917e4cd5cda5999ada163ebffd9142ea41aa7992252cda8 /opt/terraform-plugin-dir/linux_amd64/terraform-provider-aws_v1.20.0_x4
</pre>
<p>As you can see, the SHA256 hash for AWS provider saved in the &#8220;lock.json&#8221; file matches the hash of  AWS provider v1.20 saved in the cache directory.</p>
<p>The post <a href="https://tailored.cloud/devops/cache-terraform-providers/">Cache your Terraform providers to save space and time</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/devops/cache-terraform-providers/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Generating arbitrary configuration files from Terraform; modules and &#8220;always changed&#8221; resources.</title>
		<link>https://tailored.cloud/devops/terraform_confg_files_changed_resources/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=terraform_confg_files_changed_resources</link>
					<comments>https://tailored.cloud/devops/terraform_confg_files_changed_resources/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Thu, 12 Apr 2018 19:48:37 +0000</pubDate>
				<category><![CDATA[devops]]></category>
		<category><![CDATA[config files]]></category>
		<category><![CDATA[problems]]></category>
		<category><![CDATA[terraform]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=301</guid>

					<description><![CDATA[<p>TL;DR: use Terraform&#8217;s &#8220;null_resource&#8221; and optionally &#8220;template_file&#8221; to easily create config files for other software (like Ansible) be careful when you <a class="more-link" href="https://tailored.cloud/devops/terraform_confg_files_changed_resources/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/devops/terraform_confg_files_changed_resources/">Generating arbitrary configuration files from Terraform; modules and &#8220;always changed&#8221; resources.</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>TL;DR:</h2>
<ul>
<li>use Terraform&#8217;s &#8220;<a href="https://www.terraform.io/docs/provisioners/null_resource.html">null_resource</a>&#8221; and optionally &#8220;<a href="https://www.terraform.io/docs/providers/template/d/file.html">template_file</a>&#8221; to easily create config files for other software (like Ansible)</li>
<li>be careful when you add explicit &#8220;depends_on&#8221; attribute, as it might result in reporting submodule data as always &#8220;changed&#8221;, even if they weren&#8217;t</li>
</ul>
<h2>1. Introduction</h2>
<p><img decoding="async" class="alignleft wp-image-311" src="https://localhost:8080/wp-content/uploads/2018/04/tf.png" alt="Terraform Logo" width="216" height="216" srcset="https://tailored.cloud/wp-content/uploads/2018/04/tf.png 400w, https://tailored.cloud/wp-content/uploads/2018/04/tf-150x150.png 150w, https://tailored.cloud/wp-content/uploads/2018/04/tf-300x300.png 300w" sizes="(max-width: 216px) 100vw, 216px" />Recently, I had a strange problem with <a href="https://www.terraform.io/">Terraform</a> (all the code below was tested with Terraform 0.11.2). In our company, we use it to manage all our IaaS resources in AWS, but also to generate some configuration files for Ansible. Such configuration files need to be based on data that only Terraform has, so we use &#8220;<a href="https://www.terraform.io/docs/provisioners/null_resource.html">null_resource</a>&#8221; to generate them. But in some cases, our &#8220;null&#8221; type resources were always reported as changed, no matter if the source variables used in the resource changed or not. This quickly became a problem. When you read Terraform plan you really want to know which parts of your infrastructure are going to be changed, including Ansible config files. So, in this blog post, I&#8217;m trying to address two things: the former is a simple example of how to generate configuration files for other tools from Terraform. The latter is about nailing down the &#8220;always changed null_resource&#8221; issue.</p>
<h2>2. Working example &#8211; generating configuration files</h2>
<p>Let&#8217;s start to dig into the problem using a simple Terraform code, which allows us to generate an arbitrary configuration file based on Terraform&#8217;s configuration and resources. To do that, we combine &#8220;<a href="https://www.terraform.io/docs/providers/template/d/file.html">template_file</a>&#8221; data provider with a <a href="https://www.terraform.io/docs/provisioners/null_resource.html#triggers">&#8220;null_resource&#8221;  and its &#8220;trigger&#8221; and &#8220;provisioner&#8221;</a> properties. In the example below, we have a variable &#8220;names&#8221;, which contains a comma-separated list of values. For each value, we want to run a template using the values as the template parameter. Next, using &#8220;null_resource&#8221; and &#8220;local-exec&#8221; provisioner, we use simple &#8220;echo&#8221; shell command to output the result of rendering the template into any file we want. That way we can easily generate a whole bunch of config files, although in this example I&#8217;m putting all outputs into a single &#8220;out.txt&#8221; to keep it simpler. Here&#8217;s the Terraform code that does just that:</p>
<pre class="brush: plain; title: ; notranslate">
variable &quot;names&quot; {
  default = &quot;joe,jimmy&quot;
}

data &quot;template_file&quot; &quot;init&quot; {
  count = &quot;${length(split(&quot;,&quot;, var.names))}&quot;
  template = &quot;${file(&quot;templates/test.template&quot;)}&quot;
  vars {
    test = &quot;${jsonencode(element(split(&quot;,&quot;, var.names), count.index))}&quot;
  }
}

resource &quot;null_resource&quot; &quot;web&quot; {
  count = &quot;${length(split(&quot;,&quot;, var.names))}&quot;
  triggers {
    template_rendered = &quot;${join(&quot;,&quot;, data.template_file.init.*.rendered)}&quot;
  }

  provisioner &quot;local-exec&quot; {
    command = &quot;echo \&quot;${join(&quot;,&quot;, data.template_file.init.*.rendered)}\&quot; &gt;&gt; out.txt&quot;
  }
}
</pre>
<p>and the &#8220;templates/test.template&#8221;:</p>
<pre class="brush: plain; title: ; notranslate">
Hello ${test}
</pre>
<p>Now we start with typical Terraform bootstrap commands:</p>
<pre class="brush: plain; title: ; notranslate">
$ terraform init
$ terraform apply
</pre>
<p>which should produce the following output:</p>
<pre class="brush: plain; title: ; notranslate">
data.template_file.init&#x5B;1]: Refreshing state...
data.template_file.init&#x5B;0]: Refreshing state...

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

+ null_resource.web&#x5B;0]
id: &lt;computed&gt;
triggers.%: &quot;1&quot;
triggers.template_rendered: &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;

+ null_resource.web&#x5B;1]
id: &lt;computed&gt;
triggers.%: &quot;1&quot;
triggers.template_rendered: &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;


Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

null_resource.web&#x5B;1]: Creating...
triggers.%: &quot;&quot; =&gt; &quot;1&quot;
triggers.template_rendered: &quot;&quot; =&gt; &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;
null_resource.web&#x5B;0]: Creating...
triggers.%: &quot;&quot; =&gt; &quot;1&quot;
triggers.template_rendered: &quot;&quot; =&gt; &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;
null_resource.web&#x5B;1]: Provisioning with 'local-exec'...
null_resource.web&#x5B;0]: Provisioning with 'local-exec'...
null_resource.web&#x5B;0] (local-exec): Executing: &#x5B;&quot;/bin/sh&quot; &quot;-c&quot; &quot;echo \&quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n\&quot; &gt;&gt; out.txt&quot;]
null_resource.web&#x5B;1] (local-exec): Executing: &#x5B;&quot;/bin/sh&quot; &quot;-c&quot; &quot;echo \&quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n\&quot; &gt;&gt; out.txt&quot;]
null_resource.web&#x5B;1]: Creation complete after 0s (ID: 4859977991312868035)
null_resource.web&#x5B;0]: Creation complete after 0s (ID: 2379833441165791621)

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
</pre>
<p>This looks perfectly valid. Let&#8217;s check the plan again with &#8220;terraform plan&#8221;:</p>
<pre class="brush: plain; title: ; notranslate">
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.template_file.init&#x5B;1]: Refreshing state...
data.template_file.init&#x5B;0]: Refreshing state...
null_resource.web&#x5B;1]: Refreshing state... (ID: 4859977991312868035)
null_resource.web&#x5B;0]: Refreshing state... (ID: 2379833441165791621)

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
</pre>
<p>No changes detected, as can be expected. Perfect. Just to confirm, our &#8220;out.txt&#8221; file contains now:</p>
<pre class="brush: plain; title: ; notranslate">
Hello joe
,Hello jimmy
Hello joe
,Hello jimmy
</pre>
<h2>3. Refactoring: creating a module</h2>
<p>In real life, TerraForm code needs to be organized into modules to keep it more structured and manageable. So let&#8217;s start creating a very simple and minimalistic module, that just returns a constant variable for us. We create &#8220;modules/dummy/main.tf&#8221; file with the following content:</p>
<pre class="brush: plain; title: ; notranslate">
output &quot;file_path&quot; {
  value = &quot;out.txt&quot;
}
</pre>
<p>We also include this module in our &#8220;test.tf&#8221; file by adding the following lines:</p>
<pre class="brush: plain; title: ; notranslate">
module &quot;dummy-1&quot; {
  source = &quot;./modules/dummy&quot;
}
</pre>
<p>Just for testing, let&#8217;s remove the configuration file &#8220;out.txt&#8221; and the state file. Now, let&#8217;s run &#8220;apply&#8221;:</p>
<pre class="brush: plain; title: ; notranslate">
data.template_file.init&#x5B;1]: Refreshing state...
data.template_file.init&#x5B;0]: Refreshing state...

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

+ null_resource.web&#x5B;0]
id: &lt;computed&gt;
triggers.%: &quot;1&quot;
triggers.template_rendered: &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;

+ null_resource.web&#x5B;1]
id: &lt;computed&gt;
triggers.%: &quot;1&quot;
triggers.template_rendered: &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

null_resource.web&#x5B;0]: Creating...
triggers.%: &quot;&quot; =&gt; &quot;1&quot;
triggers.template_rendered: &quot;&quot; =&gt; &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;
null_resource.web&#x5B;1]: Creating...
triggers.%: &quot;&quot; =&gt; &quot;1&quot;
triggers.template_rendered: &quot;&quot; =&gt; &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot;
null_resource.web&#x5B;1]: Provisioning with 'local-exec'...
null_resource.web&#x5B;0]: Provisioning with 'local-exec'...
null_resource.web&#x5B;0] (local-exec): Executing: &#x5B;&quot;/bin/sh&quot; &quot;-c&quot; &quot;echo \&quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n\&quot; &gt;&gt; out.txt&quot;]
null_resource.web&#x5B;1] (local-exec): Executing: &#x5B;&quot;/bin/sh&quot; &quot;-c&quot; &quot;echo \&quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n\&quot; &gt;&gt; out.txt&quot;]
null_resource.web&#x5B;1]: Creation complete after 0s (ID: 6827055886347134598)
null_resource.web&#x5B;0]: Creation complete after 0s (ID: 7071338054223239370)

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
</pre>
<p>OK, works as expected. Let&#8217;s confirm the state by running &#8220;plan&#8221; again:</p>
<pre class="brush: plain; title: ; notranslate">
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.template_file.init&#x5B;0]: Refreshing state...
data.template_file.init&#x5B;1]: Refreshing state...
null_resource.web&#x5B;1]: Refreshing state... (ID: 6827055886347134598)
null_resource.web&#x5B;0]: Refreshing state... (ID: 7071338054223239370)

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
</pre>
<p>So far so good.</p>
<h2>4. Module dependency and the &#8220;always changed&#8221; problem</h2>
<p>In real life, modules frequently depend on values returned by other modules. Normally, Terraform does a good job at detecting these dependencies automatically. If it misses such dependency and still one module relies on results of the other, you should manually add the dependency information by adding &#8220;depends_on&#8221; attribute. You might even put it in your code just in case, to be sure the execution order is correct. So, to demonstrate that, let&#8217;s add an explicit dependency between our &#8220;init&#8221; template_file resource and our module. We need to add the line:</p>
<pre class="brush: plain; title: ; notranslate">
depends_on = &#x5B;&quot;module.dummy-1&quot;]
</pre>
<p>in our &#8220;init&#8221; resource section. Let&#8217;s ask Terraform what it thinks about this change by running &#8220;plan&#8221;:</p>
<pre class="brush: plain; title: ; notranslate">
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

null_resource.web&#x5B;1]: Refreshing state... (ID: 6827055886347134598)
null_resource.web&#x5B;0]: Refreshing state... (ID: 7071338054223239370)

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
&lt;= read (data resources)

Terraform will perform the following actions:

&lt;= data.template_file.init&#x5B;0]
id: &lt;computed&gt;
rendered: &lt;computed&gt;
template: &quot;Hello ${test}\n&quot;
vars.%: &quot;1&quot;
vars.test: &quot;\&quot;joe\&quot;&quot;

&lt;= data.template_file.init&#x5B;1]
id: &lt;computed&gt;
rendered: &lt;computed&gt;
template: &quot;Hello ${test}\n&quot;
vars.%: &quot;1&quot;
vars.test: &quot;\&quot;jimmy\&quot;&quot;

-/+ null_resource.web&#x5B;0] (new resource required)
id: &quot;7071338054223239370&quot; =&gt; &lt;computed&gt; (forces new resource)
triggers.%: &quot;1&quot;=&gt; &lt;computed&gt; (forces new resource)
triggers.template_rendered: &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot; =&gt; &quot;&quot; (forces new resource)

-/+ null_resource.web&#x5B;1] (new resource required)
id: &quot;6827055886347134598&quot; =&gt; &lt;computed&gt; (forces new resource)
triggers.%: &quot;1&quot; =&gt; &lt;computed&gt; (forces new resource)
triggers.template_rendered: &quot;Hello \&quot;joe\&quot;\n,Hello \&quot;jimmy\&quot;\n&quot; =&gt; &quot;&quot; (forces new resource)

Plan: 2 to add, 0 to change, 2 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an &quot;-out&quot; parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
&quot;terraform apply&quot; is subsequently run.
</pre>
<p>That&#8217;s a bummer. From now on, every &#8220;plan&#8221; or &#8220;apply&#8221; run will report both our &#8220;init&#8221; file resources as changed and they will be regenerated on every Terraform run.</p>
<h2>5. Summary</h2>
<p>You can definitely use null resource to generate config files for another software. Still, it seems there&#8217;s a bug in the current (0.11.2) version of Terraform. It&#8217;s present also in few previous versions I checked. So, be careful when you add explicit dependencies between modules and other resources, as this might result in your &#8220;null&#8221; type resources to be always reported as changed.</p>
<p>The post <a href="https://tailored.cloud/devops/terraform_confg_files_changed_resources/">Generating arbitrary configuration files from Terraform; modules and &#8220;always changed&#8221; resources.</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/devops/terraform_confg_files_changed_resources/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Advanced list operations in Ansible</title>
		<link>https://tailored.cloud/devops/advanced-list-operations-ansible/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=advanced-list-operations-ansible</link>
					<comments>https://tailored.cloud/devops/advanced-list-operations-ansible/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Fri, 16 Mar 2018 19:26:55 +0000</pubDate>
				<category><![CDATA[devops]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=284</guid>

					<description><![CDATA[<p>TL;DR &#8220;selectattr&#8221; filter in Ansible is useful for filtering lists based on attributes of the objects in the list selectattr('name', <a class="more-link" href="https://tailored.cloud/devops/advanced-list-operations-ansible/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/devops/advanced-list-operations-ansible/">Advanced list operations in Ansible</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>TL;DR</h1>
<ul>
<li>&#8220;selectattr&#8221; filter in Ansible is useful for filtering lists based on attributes of the objects in the list
<ul>
<li>
<pre class="brush: yaml; title: ; notranslate"> selectattr('name', 'match', 'eth&#x5B;2-9]') </pre>
</li>
</ul>
</li>
<li>Ansible&#8217;s &#8220;sum&#8221; filter can be used for reducing lists into new objects (including lists)
<ul>
<li>
<pre class="brush: yaml; title: ; notranslate">sum(attribute='ips', start=&#x5B;]) </pre>
</li>
</ul>
</li>
</ul>
<h1>Advanced list operations in Ansible</h1>
<h2>Intro</h2>
<p><img loading="lazy" decoding="async" class="alignright wp-image-277 size-full" src="https://localhost:8080/wp-content/uploads/2017/12/ansible.png" alt="Ansible" width="225" height="225" srcset="https://tailored.cloud/wp-content/uploads/2017/12/ansible.png 225w, https://tailored.cloud/wp-content/uploads/2017/12/ansible-150x150.png 150w" sizes="(max-width: 225px) 100vw, 225px" />In the <a href="http://localhost:8080/devops/how-to-filter-and-map-lists-in-ansible/">previous entry</a>, I gave you an overview of how you can do all the basic operations on lists in Ansible. Soon after that, I faced a problem, that was still solvable without writing an <a href="http://docs.ansible.com/ansible/2.3/dev_guide/developing_plugins.html">Ansible plugin</a>. Using list filters was enough, yet the task definitely wasn&#8217;t simple. Let me show you what the challenge was and how you can combine some advanced <a href="http://docs.ansible.com/ansible/latest/playbooks_filters.html">list filters</a> in Ansible to perform more complicated list tasks.</p>
<h2>The problem</h2>
<p>In our company, we use Ansible to configure some multi-interface multi-IP networking instances running in AWS. These instances are used to manage client&#8217;s IPs, so we need to have full control over which IP is assigned where. To deal with that, we have an Ansible role that reads a YAML file in the following format for each of instance&#8217;s interfaces:</p>
<pre class="brush: yaml; title: ; notranslate">
# file host_vars/HOST/interfaces-eth1.yml
interfaces_eth1:
- gw: 10.10.10.1
  name: eth1
  ips:
  - {ip: 10.10.10.104, owner: TEST, project: The Project}
  - {ip: 10.10.10.105, owner: Our Client, project: The Project} 
</pre>
<pre class="brush: yaml; title: ; notranslate"> 
# file host_vars/HOST/interfaces-eth2.yml 
interfaces_eth2: 
- gw: 10.10.10.1 ips:
  name: eth2
  - {ip: 10.10.10.204, owner: TEST, project: The Project}
  - {ip: 10.10.10.205, owner: Our Client, project: The Project}
  - {ip: 10.10.10.206, owner: Our Client, project: The Project}
  - {ip: 10.10.10.207, owner: Our Client, project: The Project}
  - {ip: 10.10.10.208, owner: Our Client, project: The Project}
  - {ip: 10.10.10.209, owner: Our Client, project: The Project}
</pre>
<p>Then, we combine all per-interface configs into a single config file for an instance like this:</p>
<pre class="brush: yaml; title: ; notranslate">
# file host_vars/HOST/interfaces.yml
interfaces: &quot;{{ interfaces_eth1 }} + {{ interfaces_eth2 }}&quot;
</pre>
<p>Now, the problem we were facing is: on each instance, we have at least 1 IP address dedicated to monitoring and checking if the given interface is up and running. Such IP address has the owner field set to &#8220;TEST&#8221;. So, the challenge is to find at least one &#8220;TEST&#8221; IP on each instance. Basically, we want the first &#8220;TEST&#8221; IP address out of all addresses assigned to all interfaces of any single networking machine.  Additionally, we have to ignore &#8220;eth0&#8221; and &#8220;eth1&#8221; interfaces, as they are being used for other purposes.</p>
<h2>The solution, Ansible and Jinja2 way</h2>
<p>If you want to test the solution on your local host, create 3 files in the directory &#8216;host_vars/localhost&#8217; as above. The problem can be solved with the following ansible code. Let&#8217;s first have a look and then I will explain it step by step:</p>
<pre class="brush: yaml; title: ; notranslate">
# file playbook.yml
- hosts: all
 tasks:
 - set_fact:
 test_ip: &quot;{{ interfaces 
 | selectattr('name', 'match', 'eth&#x5B;2-9]') 
 | sum(attribute='ips', start=&#x5B;]) 
 | selectattr('owner', 'equalto', 'TEST') 
 | map(attribute='ip') 
 | list 
 | first 
 | default('NOT_FOUND') }}&quot;
 - debug:
 msg: &quot;The TEST IP is {{ test_ip }}&quot;
</pre>
<p>In line 5, we start with our list of interface configurations, created from both &#8220;interfaces_eth1&#8221; and &#8220;interfaces_eth2&#8221;, which are one-element lists.</p>
<p>Line 6 uses &#8220;selectattr&#8221; filter, which acts as a filter for a list of objects. This filter passes only objects matching certain criteria. In our case, the two-element list of interface configuration blocks is checked one element at a time and only blocks having attribute &#8220;name&#8221; matching a regular expression &#8220;eth[2-9]&#8221; are passed forward. The fact that we want to match by a regular expression is reflected by the 2nd argument set to &#8220;match&#8221;.</p>
<p>Then, line 7 does a nice trick: it&#8217;s a reduction operator that acts on all elements of a list and produces a single resulting object. Its arguments are object&#8217;s attribute to act on and the initial state. The operand applied here for reduction is of course &#8220;sum&#8221;. So, we start with an empty list (the 2nd argument) and then for each interface definition block on our list, we add to the empty list the attribute &#8220;ips&#8221; of the interface block. The result is a single list of all IP addresses assigned to all interfaces that were not filtered out by the previous line.</p>
<p>Next, in line 8, from all the IPs on the list, we select only ones that have the attribute &#8220;owner&#8221; equal to &#8220;TEST&#8221;. Now, things get simpler. We want only the IP address, without &#8220;owner&#8221; and &#8221; project&#8221; attributes, so we &#8220;map&#8221; every object into a new one by selecting only the attribute &#8220;ip&#8221;. In line 9 we turn the sequence of IPs into a list again. In line 10 we select just the first element of the list, in case we have found more than one. If we found none, we add &#8220;default&#8221; filter at the very end.</p>
<p>OK, let&#8217;s run it.</p>
<pre class="brush: bash; title: ; notranslate">
$ ansible-playbook -c local -i 'localhost,' playbook.yml

PLAY &#x5B;all] *********************************************************

TASK &#x5B;Gathering Facts] *********************************************
ok: &#x5B;localhost]

TASK &#x5B;set_fact] ****************************************************
ok: &#x5B;localhost]

TASK &#x5B;debug] *******************************************************
ok: &#x5B;localhost] {
 &quot;msg&quot;: &quot;The TEST IP is 10.10.10.204&quot;
}

PLAY RECAP *********************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 
</pre>
<p>And it works! Tested with Ansible 2.4.3.0, Jinja2 2.9.6 and python 2.7.14.</p>
<p>The post <a href="https://tailored.cloud/devops/advanced-list-operations-ansible/">Advanced list operations in Ansible</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/devops/advanced-list-operations-ansible/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to filter, join and map lists in Ansible</title>
		<link>https://tailored.cloud/devops/how-to-filter-and-map-lists-in-ansible/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-filter-and-map-lists-in-ansible</link>
					<comments>https://tailored.cloud/devops/how-to-filter-and-map-lists-in-ansible/#respond</comments>
		
		<dc:creator><![CDATA[tc-admin]]></dc:creator>
		<pubDate>Sun, 31 Dec 2017 14:46:25 +0000</pubDate>
				<category><![CDATA[devops]]></category>
		<category><![CDATA[ansible]]></category>
		<category><![CDATA[howto]]></category>
		<guid isPermaLink="false">http://localhost:8080/?p=230</guid>

					<description><![CDATA[<p>TL;DR use &#8220;select&#8221; filter to filter a list and &#8220;match&#8221; to combine it with reg exps, like: &#34;{{ ansible_interfaces &#124; <a class="more-link" href="https://tailored.cloud/devops/how-to-filter-and-map-lists-in-ansible/">Continue Reading</a></p>
<p>The post <a href="https://tailored.cloud/devops/how-to-filter-and-map-lists-in-ansible/">How to filter, join and map lists in Ansible</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>TL;DR</h1>
<ul>
<li style="list-style-type: none;">
<ul>
<li>use <a href="http://jinja.pocoo.org/docs/2.10/templates/#select">&#8220;select&#8221;</a> filter to filter a list and &#8220;match&#8221; to combine it with reg exps, like:
<pre class="brush: plain; title: ; notranslate">&quot;{{ ansible_interfaces | select('match', '^(eth|wlan)&#x5B;0-9]+') | list }}&quot;</pre>
</li>
<li>use <a href="http://jinja.pocoo.org/docs/2.10/templates/#map">&#8220;map&#8221;</a> to map list elements, like:
<pre class="brush: plain; title: ; notranslate">&quot;{{ ansible_interfaces | map('upper') | list }}&quot;</pre>
</li>
<li>use just &#8220;+&#8221; operator to combine two lists into one, like:
<pre class="brush: plain; title: ; notranslate">&quot;{{ ansible_interfaces + &#x5B;\&quot;VETH-1\&quot;, \&quot;VETH-2\&quot;] }}&quot;</pre>
</li>
</ul>
</li>
</ul>
<ul>
<li style="list-style-type: none;">
<ul>
<li>check here for more advanced example: <a href="http://localhost:8080/devops/advanced-list-operations-ansible/">Advanced list operations in Ansible</a></li>
</ul>
</li>
</ul>
<h1>Ansible filters and lists operators</h1>
<p><img loading="lazy" decoding="async" class="alignleft size-full wp-image-277" src="https://localhost:8080/wp-content/uploads/2017/12/ansible.png" alt="" width="225" height="225" srcset="https://tailored.cloud/wp-content/uploads/2017/12/ansible.png 225w, https://tailored.cloud/wp-content/uploads/2017/12/ansible-150x150.png 150w" sizes="(max-width: 225px) 100vw, 225px" /><a href="http://docs.ansible.com/">Ansible</a> provides a rich set of filters, which you can apply to your variables. Recently, I needed to filter and map a list of host interfaces. I couldn&#8217;t remind myself how to do this, so I jumped to <a href="http://docs.ansible.com/ansible/latest/playbooks_filters.html">ansible filters docs</a>. But I couldn&#8217;t find it there. Well, you have to remember, that expression evaluation in Ansible is based on Jinja2, so you need to check <a href="http://jinja.pocoo.org/docs/2.10/templates/#builtin-filters">Jinja2 filters documentation</a> as well. You will find basic operators there.</p>
<p>When operating on lists, functional languages are a great example: by combining just a few list operators, you can get almost any desired transformation of the list. These basic operators include:</p>
<ul>
<li>filter &#8211; to select just matching elements from the list and create a new list from them</li>
<li>map &#8211; to convert all elements on the list according to transformation function that operates on a single element</li>
<li>reduce &#8211; to convert (aggregate) all elements of a list into a single value</li>
<li>flat map &#8211; to merge multiple lists into a single list</li>
</ul>
<p>Let&#8217;s now see how to perform them in Ansible 2.4.</p>
<h2>Our example &#8211; list all network interfaces</h2>
<p>As our example list we want to transform in ansible, let&#8217;s use a list of local network interfaces that Ansible discovers during setup and stores in &#8220;ansible_interfaces&#8221; list. A simple play that just fetches and prints the list can look like this:</p>
<pre class="brush: yaml; title: ; notranslate">
- hosts: all
  tasks:
    - name: print interfaces
      debug:
        msg: &quot;{{ ansible_interfaces }}&quot;
</pre>
<p>Let&#8217;s save the above code in file &#8216;list_operators.yml&#8217; and run it with:</p>
<pre class="brush: bash; title: ; notranslate">
ansible-playbook -c local -i 'localhost,' list_operators.yml
</pre>
<p>On my PC this produces the following output:</p>
<pre class="brush: plain; title: ; notranslate">
PLAY &#x5B;all] ****************************************************************

TASK &#x5B;Gathering Facts] ****************************************************
ok: &#x5B;localhost]

TASK &#x5B;print interfaces] ***************************************************
ok: &#x5B;localhost] {
    &quot;msg&quot;: &#x5B;
        &quot;docker0&quot;, 
        &quot;lo&quot;, 
        &quot;veth3e8c318&quot;, 
        &quot;wlan0&quot;,
        &quot;eth0&quot;, 
        &quot;vethe2459c9&quot;
    ]
}

PLAY RECAP ****************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
</pre>
<h2>Filter a list in Ansible</h2>
<p>Let&#8217;s assume we want to filter the interfaces list to include only &#8220;eth&#8221; or &#8220;wlan&#8221; types of interfaces. As you can find <a href="http://jinja.pocoo.org/docs/2.10/templates/#select">here in Jinja2</a> docs, we can use the &#8220;select&#8221; filter to filter the list. But the great thing &#8211; and not that easy to find &#8211; is that we can combine &#8220;select&#8221; with &#8220;match&#8221; operator to test for matching against a regular expression! This brings us to the following solution:</p>
<pre class="brush: yaml; title: ; notranslate">
- hosts: all
  tasks:
    - name: print interfaces
      debug:
        msg: &quot;{{ ansible_interfaces | select('match', '^(eth|wlan)&#x5B;0-9]+') | list }}&quot;
</pre>
<p>So, what it does is that it passes each element on the list &#8220;ansible_interfaces&#8221; to a &#8220;match&#8221; test. This test checks with the regexp if the element starts with &#8220;eth&#8221; or &#8220;wlan&#8221; followed by at least 1 digit. Elements that match are combined back again into a list with the &#8220;list&#8221; filter. The code above produces the following output:</p>
<pre class="brush: plain; title: ; notranslate">
TASK &#x5B;print interfaces] ***************************************************
ok: &#x5B;localhost] {
    &quot;msg&quot;: &#x5B;
        &quot;wlan0&quot;, 
        &quot;eth0&quot;
    ]
}
</pre>
<h2>Map list elements in Ansible</h2>
<p>OK, let&#8217;s now see how we can use the <a href="http://jinja.pocoo.org/docs/2.10/templates/#map">map filter</a> in Ansible. Let&#8217;s assume that we want to convert each interface name on our list to upper case only. Jinja2 <a href="http://jinja.pocoo.org/docs/2.10/templates/#upper">has a filter</a> for that, so our Ansible task becomes:</p>
<pre class="brush: yaml; title: ; notranslate">
- hosts: all
  tasks:
    - name: print interfaces
      debug:
        msg: &quot;{{ ansible_interfaces | map('upper') | list }}&quot;
</pre>
<p>and produces the following output:</p>
<pre class="brush: plain; title: ; notranslate">
TASK &#x5B;print interfaces] ***************************************************
ok: &#x5B;localhost] {
    &quot;msg&quot;: &#x5B;
        &quot;DOCKER0&quot;, 
        &quot;LO&quot;, 
        &quot;VETH3E8C318&quot;, 
        &quot;WLAN0&quot;, 
        &quot;DOCKERCON&quot;, 
        &quot;ETH0&quot;, 
        &quot;VETHE2459C9&quot;
    ]
}
</pre>
<h2>Merge two lists into one in Ansible</h2>
<p>Now, what if we want to merge two lists into a single one that contains elements of both of them? That&#8217;s really easy, as the normal &#8220;+&#8221; operator works for lists and does exactly that. So, running this code:</p>
<pre class="brush: yaml; title: ; notranslate">
- hosts: all
  tasks:
    - name: print interfaces
      debug:
        msg: &quot;{{ ansible_interfaces + &#x5B;\&quot;VETH-1\&quot;, \&quot;VETH-2\&quot;] }}&quot;
</pre>
<p>produces the following output, where the list of local interfaces is extended with elements &#8220;VETH-1&#8221; and &#8220;VETH-2&#8221; from the second list:</p>
<pre class="brush: plain; title: ; notranslate">
TASK &#x5B;print interfaces] ***************************************************
ok: &#x5B;localhost] {
    &quot;msg&quot;: &#x5B;
        &quot;docker0&quot;, 
        &quot;lo&quot;, 
        &quot;veth3e8c318&quot;, 
        &quot;wlan0&quot;, 
        &quot;dockercon&quot;, 
        &quot;eth0&quot;, 
        &quot;vethe2459c9&quot;, 
        &quot;VETH-1&quot;, 
        &quot;VETH-2&quot;
    ]
}
</pre>
<h2>Aggregate / reduce</h2>
<p>Here the problem is trickier &#8211; it depends on what you really want to do with list&#8217;s elements. Probably the most popular aggregation filter is <a href="http://jinja.pocoo.org/docs/2.10/templates/#join">&#8220;join&#8221;</a>, which just joins list elements into a single stream with elements separated by a given separator. There are a few more, but in general, when you need something more complicated, you either have to escape to a full Jinja2 template and do something custom in a &#8220;{% for %}&#8221; loop or <a href="http://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#filter-plugins">implement your own filter.</a></p>
<h2>Related</h2>
<p>This article also has a continuation: <a style="background-color: #ffffff;" href="http://localhost:8080/devops/advanced-list-operations-ansible/">Advanced list operations in Ansible</a></p>
<p>The post <a href="https://tailored.cloud/devops/how-to-filter-and-map-lists-in-ansible/">How to filter, join and map lists in Ansible</a> appeared first on <a href="https://tailored.cloud">Tailored Cloud</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tailored.cloud/devops/how-to-filter-and-map-lists-in-ansible/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
