<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: César M. Cristóbal</title>
    <description>The latest articles on DEV Community by César M. Cristóbal (@jilgue).</description>
    <link>https://dev.to/jilgue</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F812900%2F2dc044ea-1f10-4075-8382-b8170f10e962.jpeg</url>
      <title>DEV Community: César M. Cristóbal</title>
      <link>https://dev.to/jilgue</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jilgue"/>
    <language>en</language>
    <item>
      <title>Secrets in ArgoCD with Sops</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Wed, 07 Sep 2022 06:46:46 +0000</pubDate>
      <link>https://dev.to/callepuzzle/secrets-in-argocd-with-sops-fc9</link>
      <guid>https://dev.to/callepuzzle/secrets-in-argocd-with-sops-fc9</guid>
      <description>&lt;p&gt;&lt;a href="https://argoproj.github.io/cd/"&gt;ArgoCD&lt;/a&gt; is a tool that implements the gitops philosophy for deploying applications on Kubernetes. It is a declarative, Git-based deployment system that uses a simple, human-readable manifest file to define the desired state of your application. In this article, we will explore how to use ArgoCD with &lt;a href="https://github.com/mozilla/sops"&gt;Sops&lt;/a&gt; to manage secrets in a gitops workflow.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://github.com/mozilla/sops#encrypting-using-gcp-kms"&gt;sops with GCP KMS&lt;/a&gt;, the first thing we need is a service account with &lt;code&gt;roles/cloudkms.cryptoKeyDecrypter&lt;/code&gt; role and create a secret on Kubernetes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl create secret generic google-sa --from-file sa.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The idea is to use Argo &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/config-management-plugins/"&gt;Config Management Plugin&lt;/a&gt; to decrypt the secret and generate a plane yaml with all Kubernetes objects (deployment, services, ...) and use &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/custom_tools/"&gt;Cumstom Tooling&lt;/a&gt; to install Sops and other tools required.&lt;/p&gt;

&lt;p&gt;In the process we have encountered several problems, and one of them is that it is necessary to use yq to correctly format the output yamls and avoid errors such as: &lt;code&gt;error converting YAML to JSON: yaml: line 4: did not find expected ',' or ']'.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These are the values to install argo-cd by Helm. Configuration assumes that all secrets are in &lt;code&gt;secrets.enc&lt;/code&gt; file coding with yaml.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;configManagementPlugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;- name: sops&lt;/span&gt;
        &lt;span class="s"&gt;init:&lt;/span&gt;
          &lt;span class="s"&gt;command: ["/bin/sh", "-c"]&lt;/span&gt;
          &lt;span class="s"&gt;args: ["echo '---' &amp;gt; secrets.yaml &amp;amp;&amp;amp; sops -d --input-type yaml --output-type yaml secrets.enc &amp;gt;&amp;gt; secrets.yaml"]&lt;/span&gt;
        &lt;span class="s"&gt;generate:&lt;/span&gt;
          &lt;span class="s"&gt;command: ["/bin/sh", "-c"]&lt;/span&gt;
          &lt;span class="s"&gt;args: ["source /virtualenv-python/bin/activate; pip install yq; cat *.yaml | yq -y"]&lt;/span&gt;
&lt;span class="na"&gt;repoServer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;custom-tools&lt;/span&gt;
      &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;virtualenv-python&lt;/span&gt;
      &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-sa&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-sa&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sa.json&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sa.json&lt;/span&gt;
  &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/sops&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;custom-tools&lt;/span&gt;
      &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sops&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/jq&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;custom-tools&lt;/span&gt;
      &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jq&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/secrets/sa.json&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-sa&lt;/span&gt;
      &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sa.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/virtualenv-python&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;virtualenv-python&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/secrets/sa.json&lt;/span&gt;
  &lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;custom-tools&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:3.8&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wget https://github.com/mozilla/sops/releases/download/v3.7.3/sops-v3.7.3.linux.amd64;&lt;/span&gt;
          &lt;span class="s"&gt;chmod a+x sops-v3.7.3.linux.amd64;&lt;/span&gt;
          &lt;span class="s"&gt;mv sops-v3.7.3.linux.amd64 /custom-tools/sops;&lt;/span&gt;
          &lt;span class="s"&gt;wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64;&lt;/span&gt;
          &lt;span class="s"&gt;chmod a+x jq-linux64;&lt;/span&gt;
          &lt;span class="s"&gt;mv jq-linux64 /custom-tools/jq;&lt;/span&gt;
      &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/custom-tools&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;custom-tools&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;virtualenv-python&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.7&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3 -m venv /virtualenv-python&lt;/span&gt;
      &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/virtualenv-python&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;virtualenv-python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;helm upgrade sops argo/argo-cd --values values-sops.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And an application example to try it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/Callepuzzle/manifests&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;poc-argocd&lt;/span&gt;
    &lt;span class="na"&gt;plugin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sops&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://kubernetes.default.svc'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>tutorial</category>
      <category>kubernetes</category>
      <category>security</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Grafana multi-tenant configuration with Terraform</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 12:08:08 +0000</pubDate>
      <link>https://dev.to/callepuzzle/grafana-multi-tenant-configuration-with-terraform-1d49</link>
      <guid>https://dev.to/callepuzzle/grafana-multi-tenant-configuration-with-terraform-1d49</guid>
      <description>&lt;p&gt;A way to configure a multi-tenant environment in Grafana is to use organization to split each tenant. But, how can I configure this by IaC?&lt;/p&gt;

&lt;p&gt;Grafana provides an active provisioning system that uses config files. Data sources and dashboards can be defined via files which are version controlled.&lt;/p&gt;

&lt;p&gt;There are many tools to manage these config files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://forge.puppet.com/puppet/grafana"&gt;Puppet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudalchemy/ansible-grafana"&gt;Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/JonathanTron/chef-grafana"&gt;Chef&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/salt-formulas/salt-formula-grafana"&gt;Saltstack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/grafana/grafonnet-lib/"&gt;Jsonnet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grafana provisioning allows the configuration of data sources, plugins, dashboards and alert notification channels. All of these “objects” can be created in a specific organization.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is great, what else would you like?&lt;br&gt;
I would like a little bit more. What happens with the organization or the users? Can I configure them by IaC?&lt;br&gt;
Yes, you can, and Terraform is going to help with that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Grafana provider
&lt;/h2&gt;

&lt;p&gt;Grafana has an &lt;a href="https://registry.terraform.io/providers/grafana/grafana/latest/docs"&gt;official Terraform provider&lt;/a&gt; which includes resources for &lt;a href="https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/user"&gt;users&lt;/a&gt; and &lt;a href="https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/organization"&gt;organizations&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tenant configuration
&lt;/h2&gt;

&lt;p&gt;For managing resources in different organizations with Terraform you have to configure Grafana’s provider with the organization ID.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"grafana"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:3000"&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin:admin"&lt;/span&gt;
  &lt;span class="nx"&gt;org_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, the idea is having two different providers using &lt;a href="https://www.terraform.io/language/providers/configuration#alias-multiple-provider-configurations"&gt;alias&lt;/a&gt;. The first creates an organization and an admin user with the principal admin user. And the second uses organization and users created in the previous step.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"grafana"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:3000"&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin:admin"&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"grafana"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:3000"&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin_org_2:pass_org_2"&lt;/span&gt;
  &lt;span class="nx"&gt;org_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"config"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Full example
&lt;/h2&gt;

&lt;p&gt;Enough theory, let’s take a practical example. For this you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.terraform.io/downloads"&gt;Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kind.sigs.k8s.io/"&gt;Kind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Cloning this repository: &lt;a href="https://github.com/jilgue/medium-grafana-multi-tenant"&gt;https://github.com/jilgue/medium-grafana-multi-tenant&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sbstp/kubie"&gt;Kubie&lt;/a&gt; (not necessary)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deploy Grafana in Kubernetes cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kind create cluster
$ kubie ctx kind-kind
$ cd 010-environment/010-grafana
$ terraform init
$ terraform apply
$ terraform output admin_password
$ kubectl port-forward service/grafana 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our Grafana is accessible from &lt;a href="http://127.0.0.1:3000"&gt;http://127.0.0.1:3000&lt;/a&gt;. Let’s create a new organization with its admin user and resources (a folder for this example)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd ../../020-client-1/010-grafana-config/
$ terraform init
$ terraform apply
$ terraform output password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now logging with admin or client-1 user and switching the organization we will see the folder created.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:3000/dashboards?orgId=2"&gt;http://localhost:3000/dashboards?orgId=2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0SzGGwCX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/113fg0t1l8e5rjjsil5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0SzGGwCX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/113fg0t1l8e5rjjsil5w.png" alt="http://localhost:3000/dashboards?orgId=2" width="536" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>grafana</category>
      <category>terraform</category>
    </item>
    <item>
      <title>How to create custom pod with Kubebuilder</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 11:59:55 +0000</pubDate>
      <link>https://dev.to/callepuzzle/how-to-create-custom-pod-with-kubebuilder-bd7</link>
      <guid>https://dev.to/callepuzzle/how-to-create-custom-pod-with-kubebuilder-bd7</guid>
      <description>&lt;p&gt;In this post we’ll implement a simple Kubernetes controller using the &lt;a href="https://github.com/kubernetes-sigs/kubebuilder"&gt;kubebuilder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Kubebuilder has a &lt;a href="https://book.kubebuilder.io/"&gt;book&lt;/a&gt; but I think that it is too complex for beginning users. I’m going to try to do it easier. We’ll implement a simple operator which manage a pod.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Kubebuilder
&lt;/h2&gt;

&lt;p&gt;Kubebuilder doesn’t support Go 1.17, so we need to install Go 1.16. I decided to use &lt;a href="https://github.com/syndbg/goenv"&gt;goenv&lt;/a&gt; to manager Go versions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove previous Go version&lt;/li&gt;
&lt;li&gt;Install goenv: &lt;a href="https://github.com/syndbg/goenv/blob/master/INSTALL.md"&gt;Install goenv: https://github.com/syndbg/goenv/blob/master/INSTALL.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install Go 1.16: &lt;code&gt;$ goenv install 1.16.8&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use this version: &lt;code&gt;$ goenv global 1.16.8&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install Kubebuilder:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -L -o ~/.local/bin/kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
$ chmod a+x ~/.local/bin/kubebuilder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initialize a project and create new API
&lt;/h2&gt;

&lt;p&gt;We have to follow three steps to create a new white project with one custom resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir medium-kubebuilder-pod
$ cd !$
$ git init
$ go mod init simplepod
$ kubebuilder init --domain callepuzzle.com
$ git add .
$ git commit -m "init"
$ kubebuilder create api --group medium --version v1alpha1 --kind SimplePod
$ git add .
$ git commit -m "create api"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we have all the necessary scaffolding for our project.&lt;/p&gt;

&lt;p&gt;If we now run &lt;code&gt;make install&lt;/code&gt;, kubebuilder should generate the base CRDs under &lt;code&gt;config/crd/bases&lt;/code&gt; and a few other files for us. Running &lt;code&gt;make run&lt;/code&gt; should now allow us to launch the operator locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make install
/home/cesar/projects/k8s-operator/medium-kubebuilder-pod/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
go: creating new go.mod: module tmp
Downloading sigs.k8s.io/kustomize/kustomize/v3@v3.8.7
go get: added sigs.k8s.io/kustomize/kustomize/v3 v3.8.7
/home/cesar/projects/k8s-operator/medium-kubebuilder-pod/bin/kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/simplepods.medium.callepuzzle.com created$ kubectl get customresourcedefinitions.apiextensions.k8s.io simplepods.medium.callepuzzle.com 
NAME                                CREATED AT
simplepods.medium.callepuzzle.com   2021-10-13T15:47:52Z$ kubectl apply -f config/samples/medium_v1alpha1_simplepod.yaml$ kubectl get simplepods.medium.callepuzzle.com 
NAME               AGE
simplepod-sample   9s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, we can create “simplepod” resources but it doesn’t do anything, doesn’t have any logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make our custom resource
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;api/v1alpha1/simplepod_types.go&lt;/code&gt; is defined struct of our resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git diff api/v1alpha1/simplepod_types.go config/samples/medium_v1alpha1_simplepod.yaml
diff --git a/api/v1alpha1/simplepod_types.go b/api/v1alpha1/simplepod_types.go
index f5f3fde..e4f0630 100644
--- a/api/v1alpha1/simplepod_types.go
+++ b/api/v1alpha1/simplepod_types.go
@@ -29,7 +29,7 @@ type SimplePodSpec struct {
        // Important: Run "make" to regenerate code after modifying this file

        // Foo is an example field of SimplePod. Edit simplepod_types.go to remove/update
-       Foo string `json:"foo,omitempty"`
+       Command string `json:"command,omitempty"`
 }

 // SimplePodStatus defines the observed state of SimplePod
diff --git a/config/samples/medium_v1alpha1_simplepod.yaml b/config/samples/medium_v1alpha1_simplepod.yaml
index 671c617..dd8cda0 100644
--- a/config/samples/medium_v1alpha1_simplepod.yaml
+++ b/config/samples/medium_v1alpha1_simplepod.yaml
@@ -4,4 +4,4 @@ metadata:
   name: simplepod-sample
 spec:
   # Add fields here
-  foo: bar
+  command: ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time we change the struct of our resource we have to run make install to regenerate the manifests.&lt;/p&gt;

&lt;p&gt;Now, we implement the logic of our operator. Create a pod object and execute the command given by simplepod object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git diff controllers/simplepod_controller.go
diff --git a/controllers/simplepod_controller.go b/controllers/simplepod_controller.go
index 2f13668..89b63d0 100644
--- a/controllers/simplepod_controller.go
+++ b/controllers/simplepod_controller.go
@@ -18,7 +18,10 @@ package controllers

 import (
        "context"
+       "strings"

+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime"
        ctrl "sigs.k8s.io/controller-runtime"
        "sigs.k8s.io/controller-runtime/pkg/client"
@@ -36,6 +39,7 @@ type SimplePodReconciler struct {
 //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods,verbs=get;list;watch;create;update;patch;delete
 //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/status,verbs=get;update;patch
 //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/finalizers,verbs=update
+//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete

 // Reconcile is part of the main kubernetes reconciliation loop which aims to
 // move the current state of the cluster closer to the desired state.
@@ -47,16 +51,62 @@ type SimplePodReconciler struct {
 // For more details, check Reconcile and its Result here:
 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
 func (r *SimplePodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-       _ = log.FromContext(ctx)
+       log := log.FromContext(ctx)

-       // your logic here
+       var instance mediumv1alpha1.SimplePod
+       errGet := r.Get(ctx, req.NamespacedName, &amp;amp;instance)
+       if errGet != nil {
+               log.Error(errGet, "Error getting instance")
+               return ctrl.Result{}, client.IgnoreNotFound(errGet)
+       }
+
+       pod := NewPod(&amp;amp;instance)
+
+       _, errCreate := ctrl.CreateOrUpdate(ctx, r.Client, pod, func() error {
+               return ctrl.SetControllerReference(&amp;amp;instance, pod, r.Scheme)
+       })
+
+       if errCreate != nil {
+               log.Error(errCreate, "Error creating pod")
+               return ctrl.Result{}, nil
+       }
+
+       err := r.Status().Update(context.TODO(), &amp;amp;instance)
+       if err != nil {
+               return ctrl.Result{}, err
+       }

        return ctrl.Result{}, nil
 }

+func NewPod(pod *mediumv1alpha1.SimplePod) *corev1.Pod {
+       labels := map[string]string{
+               "app": pod.Name,
+       }
+
+       return &amp;amp;corev1.Pod{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      pod.Name,
+                       Namespace: pod.Namespace,
+                       Labels:    labels,
+               },
+               Spec: corev1.PodSpec{
+                       Containers: []corev1.Container{
+                               {
+                                       Name:    "busybox",
+                                       Image:   "busybox",
+                                       Command: strings.Split(pod.Spec.Command, " "),
+                               },
+                       },
+                       RestartPolicy: corev1.RestartPolicyOnFailure,
+               },
+       }
+}
+
 // SetupWithManager sets up the controller with the Manager.
 func (r *SimplePodReconciler) SetupWithManager(mgr ctrl.Manager) error {
        return ctrl.NewControllerManagedBy(mgr).
                For(&amp;amp;mediumv1alpha1.SimplePod{}).
+               Owns(&amp;amp;corev1.Pod{}).
                Complete(r)
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time we change the struct of our resource we have to run make install to regenerate the manifests.&lt;/p&gt;

&lt;p&gt;Now, we implement the logic of our operator. Create a pod object and execute the command given by simplepod object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git diff controllers/simplepod_controller.go
diff --git a/controllers/simplepod_controller.go b/controllers/simplepod_controller.go
index 2f13668..89b63d0 100644
--- a/controllers/simplepod_controller.go
+++ b/controllers/simplepod_controller.go
@@ -18,7 +18,10 @@ package controllers

 import (
        "context"
+       "strings"

+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime"
        ctrl "sigs.k8s.io/controller-runtime"
        "sigs.k8s.io/controller-runtime/pkg/client"
@@ -36,6 +39,7 @@ type SimplePodReconciler struct {
 //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods,verbs=get;list;watch;create;update;patch;delete
 //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/status,verbs=get;update;patch
 //+kubebuilder:rbac:groups=medium.callepuzzle.com,resources=simplepods/finalizers,verbs=update
+//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete

 // Reconcile is part of the main kubernetes reconciliation loop which aims to
 // move the current state of the cluster closer to the desired state.
@@ -47,16 +51,62 @@ type SimplePodReconciler struct {
 // For more details, check Reconcile and its Result here:
 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
 func (r *SimplePodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-       _ = log.FromContext(ctx)
+       log := log.FromContext(ctx)

-       // your logic here
+       var instance mediumv1alpha1.SimplePod
+       errGet := r.Get(ctx, req.NamespacedName, &amp;amp;instance)
+       if errGet != nil {
+               log.Error(errGet, "Error getting instance")
+               return ctrl.Result{}, client.IgnoreNotFound(errGet)
+       }
+
+       pod := NewPod(&amp;amp;instance)
+
+       _, errCreate := ctrl.CreateOrUpdate(ctx, r.Client, pod, func() error {
+               return ctrl.SetControllerReference(&amp;amp;instance, pod, r.Scheme)
+       })
+
+       if errCreate != nil {
+               log.Error(errCreate, "Error creating pod")
+               return ctrl.Result{}, nil
+       }
+
+       err := r.Status().Update(context.TODO(), &amp;amp;instance)
+       if err != nil {
+               return ctrl.Result{}, err
+       }

        return ctrl.Result{}, nil
 }

+func NewPod(pod *mediumv1alpha1.SimplePod) *corev1.Pod {
+       labels := map[string]string{
+               "app": pod.Name,
+       }
+
+       return &amp;amp;corev1.Pod{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      pod.Name,
+                       Namespace: pod.Namespace,
+                       Labels:    labels,
+               },
+               Spec: corev1.PodSpec{
+                       Containers: []corev1.Container{
+                               {
+                                       Name:    "busybox",
+                                       Image:   "busybox",
+                                       Command: strings.Split(pod.Spec.Command, " "),
+                               },
+                       },
+                       RestartPolicy: corev1.RestartPolicyOnFailure,
+               },
+       }
+}
+
 // SetupWithManager sets up the controller with the Manager.
 func (r *SimplePodReconciler) SetupWithManager(mgr ctrl.Manager) error {
        return ctrl.NewControllerManagedBy(mgr).
                For(&amp;amp;mediumv1alpha1.SimplePod{}).
+               Owns(&amp;amp;corev1.Pod{}).
                Complete(r)
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, install our changes and run the operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ go get k8s.io/api/core/v1@v0.20.2
$ make install
$ kubectl delete -f config/samples/medium_v1alpha1_simplepod.yaml
$ kubectl apply -f config/samples/medium_v1alpha1_simplepod.yaml
$ make run
(in another terminal)
$ kubectl get pod
NAME               READY   STATUS      RESTARTS   AGE
simplepod-sample   0/1     Completed   0          3s
$ kubectl logs simplepod-sample 
bin
dev
etc
home
proc
root
sys
tmp
usr
var
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entire final code: &lt;a href="https://github.com/jilgue/medium-kubebuilder-pod"&gt;https://github.com/jilgue/medium-kubebuilder-pod&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>go</category>
    </item>
    <item>
      <title>Running Nextcloud on OPNsense</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 11:52:26 +0000</pubDate>
      <link>https://dev.to/callepuzzle/running-nextcloud-on-opnsense-1gl0</link>
      <guid>https://dev.to/callepuzzle/running-nextcloud-on-opnsense-1gl0</guid>
      <description>&lt;p&gt;In this article we’re going to see how to run &lt;a href="https://nextcloud.com/" rel="noopener noreferrer"&gt;Nextcloud&lt;/a&gt; on a &lt;a href="https://opnsense.org/" rel="noopener noreferrer"&gt;OPNsense&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first question is:&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do you do this?
&lt;/h2&gt;

&lt;p&gt;Well, I haven’t used Google services for many years, except maps. I haven’t found a good alternative to Google Maps. I had a Nextcloud installed on a virtual machine hosted in &lt;a href="https://www.hetzner.com/cloud" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt;. It’s ok, it worked perfectly. But I needed more resources, more space for photos and videos, so I decided to migrate Nextcloud to my home.&lt;/p&gt;

&lt;p&gt;I have a OPNsense router and firewall for my home. It has enough memory, so why have another device running when I can just use Nextcloud?&lt;/p&gt;

&lt;p&gt;The next question is:&lt;/p&gt;

&lt;h2&gt;
  
  
  Can OPNsense run Nextcloud?
&lt;/h2&gt;

&lt;p&gt;At first, I thought about using Docker, but the &lt;a href="https://github.com/kvasdopil/docker" rel="noopener noreferrer"&gt;FreeBSD port of Docker project&lt;/a&gt; hasn’t been updated in 6 years.&lt;/p&gt;

&lt;p&gt;The second idea was to try to install Nextcloud on FreeBSD using the &lt;a href="https://github.com/opnsense/ports" rel="noopener noreferrer"&gt;OPNsense ports&lt;/a&gt;. Bad idea. Nextcloud has a lot of dependencies, it’s too complex to install and impossible to maintain.&lt;/p&gt;

&lt;p&gt;And then I found this article &lt;a href="https://yom.iaelu.net/2020/05/freebsd-using-docker-and-kubernetes/" rel="noopener noreferrer"&gt;https://yom.iaelu.net/2020/05/freebsd-using-docker-and-kubernetes/&lt;/a&gt;. Really? Can I have a virtual machine inside OPNsense and run Docker inside this virtual machine?&lt;/p&gt;

&lt;p&gt;Perfect, welcome to Inception.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Debian virtual machine
&lt;/h2&gt;

&lt;p&gt;The project we’re going to use is &lt;a href="https://github.com/churchers/vm-bhyve" rel="noopener noreferrer"&gt;https://github.com/churchers/vm-bhyve&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cd /usr/ports/sysutils/vm-bhyve/
# make install clean
# cd /usr/ports/sysutils/grub2-bhyve/
# make install clean
# sysrc vm_enable="YES"
# mkdir -p /srv/vm
# sysrc vm_dir="/srv/vm"
# vm init
# curl -o /srv/vm/.templates/debian.conf https://raw.githubusercontent.com/churchers/vm-bhyve/master/sample-templates/debian.conf
# vm switch create -p -a 192.168.23.0/24 bridge
# vm create -t debian -s 80G debian
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, at this point we need to enabled the interface created by vm-bhyve and to allow the network traffic in OPNsense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy972t6ul74xbvb4djy3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy972t6ul74xbvb4djy3y.png" alt="opnsense interfaces"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79zqy2x4o4mx9hku64xw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79zqy2x4o4mx9hku64xw.png" alt="opnsense ip configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31mefvtwtg4bv5qtpz2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31mefvtwtg4bv5qtpz2g.png" alt="opnsense firewall"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All ready to install Debian:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# vm install debian debian-10.9.0-amd64-netinst.iso
# vm console debian
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the network configuration step, we have to select manual configuration and set a static IP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Nextcloud
&lt;/h2&gt;

&lt;p&gt;Perfect, we have a Debian machine with its own IP. The good thing about this method is that I can use the same Ansible playbook to install Nextcloud that I used in previous machine hosted in Hetzner:&lt;/p&gt;

&lt;p&gt;[In the network configuration step, we have to select manual configuration and set a static IP.&lt;br&gt;
Install Nextcloud&lt;/p&gt;

&lt;p&gt;Perfect, we have a Debian machine with its own IP. The good thing about this method is that I can use the same Ansible playbook to install Nextcloud that I used in previous machine hosted in Hetzner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/CallePuzzle/ansible-playbook-nextcloud-debian" rel="noopener noreferrer"&gt;https://github.com/CallePuzzle/ansible-playbook-nextcloud-debian&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>freebsd</category>
      <category>nextcloud</category>
    </item>
    <item>
      <title>Switchable graphics Nvidia / Intel in Linux</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 11:38:56 +0000</pubDate>
      <link>https://dev.to/callepuzzle/switchable-graphics-nvidia-intel-in-linux-2jld</link>
      <guid>https://dev.to/callepuzzle/switchable-graphics-nvidia-intel-in-linux-2jld</guid>
      <description>&lt;p&gt;Many laptops have two graphics; Intel, which saves power, and Nvidia, which gives better performance.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://wiki.archlinux.org/title/NVIDIA_Optimus#Use_switchable_graphics"&gt;several options&lt;/a&gt; to use switchable graphics, PRIME, Bumblebee, nvidia-xrun…&lt;/p&gt;

&lt;p&gt;None of these programs functions perfectly, there is always a problem. For example, Nvidia is still active in the background so there is not any power saving or Nvidia works but with any issue (games in Steam doesn’t work or not well)&lt;/p&gt;

&lt;p&gt;At the end I choose to play any video game and then find a way to disable Nvidia when I want.&lt;/p&gt;

&lt;p&gt;Following &lt;a href="https://wiki.archlinux.org/title/NVIDIA#DRM_kernel_mode_setting"&gt;Archlinux Nvidia guide&lt;/a&gt;, I created an Ansible playbook to enable and disable Nvidia&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nvidia DRM switch (disable)&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;X11&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;absent&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SDDM&lt;/span&gt;
      &lt;span class="na"&gt;blockinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/share/sddm/scripts/Xsetup&lt;/span&gt;
        &lt;span class="na"&gt;insertafter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;#!/bin/sh&lt;/span&gt;
          &lt;span class="s"&gt;# Xsetup - run as root before the login dialog appears&lt;/span&gt;
        &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GRUB&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.lineinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/default/grub&lt;/span&gt;
        &lt;span class="na"&gt;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^GRUB_CMDLINE_LINUX_DEFAULT='&lt;/span&gt;
        &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet resume=/dev/mapper/Vol-swap"&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grub&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Modules&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.lineinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/mkinitcpio.conf&lt;/span&gt;
        &lt;span class="na"&gt;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^MODULES='&lt;/span&gt;
        &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MODULES=()&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;modules&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Backlist modules&lt;/span&gt;
      &lt;span class="na"&gt;blockinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/modprobe.d/blacklist.conf&lt;/span&gt;
        &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;blacklist nvidia&lt;/span&gt;
          &lt;span class="s"&gt;blacklist nvidia_modeset&lt;/span&gt;
          &lt;span class="s"&gt;blacklist nvidia_uvm&lt;/span&gt;
          &lt;span class="s"&gt;blacklist nvidia_drm&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Re-generate the grub.cfg&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grub-mkconfig -o /boot/grub/grub.cfg&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grub.changed&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Re-generate image&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mkinitcpio -P&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;modules.changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nvidia DRM switch (enable)&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;X11&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;playbook_dir&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/10-nvidia-drm-outputclass.conf"&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0644'&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SDDM&lt;/span&gt;
      &lt;span class="na"&gt;blockinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/share/sddm/scripts/Xsetup&lt;/span&gt;
        &lt;span class="na"&gt;insertafter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;#!/bin/sh&lt;/span&gt;
          &lt;span class="s"&gt;# Xsetup - run as root before the login dialog appears&lt;/span&gt;
        &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;xrandr --setprovideroutputsource modesetting NVIDIA-0&lt;/span&gt;
          &lt;span class="s"&gt;xrandr --auto&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GRUB&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.lineinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/default/grub&lt;/span&gt;
        &lt;span class="na"&gt;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^GRUB_CMDLINE_LINUX_DEFAULT='&lt;/span&gt;
        &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 quiet resume=/dev/mapper/Vol-swap nvidia-drm.modeset=1"&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grub&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Modules&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.lineinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/mkinitcpio.conf&lt;/span&gt;
        &lt;span class="na"&gt;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^MODULES='&lt;/span&gt;
        &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MODULES=(nvidia nvidia_modeset nvidia_uvm nvidia_drm)&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;modules&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Re-generate the grub.cfg&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grub-mkconfig -o /boot/grub/grub.cfg&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grub.changed&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Re-generate image&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mkinitcpio -P&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;modules.changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The changes require a reboot the laptop but since I don’t play every day this issue is acceptable for me.&lt;/p&gt;

&lt;p&gt;There are two more commands to execute when you are using the battery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# echo '\_SB.PCI0.PEG0.PEGP._OFF' &amp;gt; /proc/acpi/call&lt;/span&gt;
&lt;span class="c"&gt;# rmmod nvidia&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands increase three or four hour using the battery, I will research how to add them to my &lt;code&gt;/etc/acpi/handler.sh&lt;/code&gt; &lt;a href="https://dev.to/callepuzzle/how-to-save-power-consumption-in-linux-4a2e"&gt;script&lt;/a&gt; to run automatically.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>laptop</category>
    </item>
    <item>
      <title>How to save power consumption in Linux</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 11:31:50 +0000</pubDate>
      <link>https://dev.to/callepuzzle/how-to-save-power-consumption-in-linux-4a2e</link>
      <guid>https://dev.to/callepuzzle/how-to-save-power-consumption-in-linux-4a2e</guid>
      <description>&lt;p&gt;&lt;a href="https://wiki.archlinux.org/title/Powertop"&gt;Powertop&lt;/a&gt; is a great tool to save battery power and with the help of another tool, &lt;a href="https://wiki.archlinux.org/title/Acpid"&gt;acpid&lt;/a&gt;, to observe the acpi event and configure it automatically.&lt;/p&gt;

&lt;p&gt;Acpid comes with predefined actions for triggered events. By default, these actions are defined in &lt;code&gt;/etc/acpi/handler.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The action “ac_adapter” has a default option&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*)
                logger "ACPI action undefined: $2"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, whether or not you are plugged in the ac, you can see the log with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;journalctl -f

ene 12 20:54:49 msi root[3888]: ACPI action undefined: ACPI0003:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case you must to change the &lt;code&gt;/etc/acpi/handler.sh&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ac_adapter)
        case "$2" in
            ACPI0003:00)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saving the changes and unplug the ac, the following appears in the log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ene 12 21:00:31 msi root[6101]: AC unpluged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The change works. Now you can add the powertop commands. This is my &lt;code&gt;handler.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ac_adapter)
        case "$2" in
            ACPI0003:00)
                case "$4" in
                    00000000)
                        powertop --auto-tune
                        echo 'on' &amp;gt; '/sys/bus/usb/devices/3-2/power/control' # USB USB Receiver [Logitech]
                        echo 'enabled' &amp;gt; '/sys/class/net/wlo1/device/power/wakeup'
                        echo 'enabled' &amp;gt; '/sys/bus/usb/devices/usb1/power/wakeup'
                        echo 'enabled' &amp;gt; '/sys/bus/usb/devices/usb2/power/wakeup'
                        echo 'enabled' &amp;gt; '/sys/bus/usb/devices/usb3/power/wakeup'
                        echo 'enabled' &amp;gt; '/sys/bus/usb/devices/usb4/power/wakeup'
                        echo 'enabled' &amp;gt; '/sys/bus/usb/devices/3-2/power/wakeup'
                        echo 'enabled' &amp;gt; '/sys/bus/usb/devices/3-5/power/wakeup'
                        echo 'enabled' &amp;gt; '/sys/bus/usb/devices/3-10/power/wakeup'
                        logger 'AC unpluged'
                        ;;
                    00000001)
                        echo 'disabled' &amp;gt; '/sys/class/net/wlo1/device/power/wakeup'
                        echo 'disabled' &amp;gt; '/sys/bus/usb/devices/usb1/power/wakeup'
                        echo 'disabled' &amp;gt; '/sys/bus/usb/devices/usb2/power/wakeup'
                        echo 'disabled' &amp;gt; '/sys/bus/usb/devices/usb3/power/wakeup'
                        echo 'disabled' &amp;gt; '/sys/bus/usb/devices/usb4/power/wakeup'
                        echo 'disabled' &amp;gt; '/sys/bus/usb/devices/3-2/power/wakeup'
                        echo 'disabled' &amp;gt; '/sys/bus/usb/devices/3-5/power/wakeup'
                        echo 'disabled' &amp;gt; '/sys/bus/usb/devices/3-10/power/wakeup'
                        logger 'AC pluged'
                        ;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, this doesn’t work if you start the laptop without an ac. How do you solve it?&lt;br&gt;
Inspired by the following &lt;a href="https://bbs.archlinux.org/viewtopic.php?id=139980"&gt;post&lt;/a&gt; I created a new systemd unit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/lib/systemd/system/acpid-boot.service

[Unit]
Description=ACPI boot handle
Documentation=man:acpid(8)

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/acpi-boot-handle.sh

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the script &lt;code&gt;/usr/local/sbin/acpi-boot-handle.sh&lt;/code&gt; contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;acpi &lt;span class="nt"&gt;-a&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;on&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;
&lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;onBit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;onBit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;fi&lt;/span&gt;
/etc/acpi/handler.sh ac_adapter ACPI0003:00 foo 0000000&lt;span class="nv"&gt;$onBit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way you simulate the acpi action when the laptop starts.&lt;/p&gt;

&lt;p&gt;In my case a MSI Modern 14 the battery has 3 hours more when all of the powertop options are enabled.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>laptop</category>
    </item>
    <item>
      <title>Como ejecutar varios tests en Terratest</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 09:31:27 +0000</pubDate>
      <link>https://dev.to/callepuzzle/como-ejecutar-varios-tests-en-terratest-4l8o</link>
      <guid>https://dev.to/callepuzzle/como-ejecutar-varios-tests-en-terratest-4l8o</guid>
      <description>&lt;p&gt;Terratest es un framework para ejecutar test en Terraform. En este caso yo tenía un módulo muy sencillo que compone un nombre y quería probar que todas las posibles entradas funcionasen.&lt;/p&gt;

&lt;p&gt;A la hora de ejecutar varios tests en Terratest, lo primero que se me vino a la cabeza fue crear varias funciones con cada caso que quería probar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gruntwork-io/terratest/modules/terraform"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/stretchr/testify/assert"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestTerraformComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"avs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"company"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hb01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hub01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"project"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"shs01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"weu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dns"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"num"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"avs-hb01-hub01-shs01-weu-dns-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestTerraformNoFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"avs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"company"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hb01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hub01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"project"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"shs01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"weu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"num"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"avs-hb01-hub01-shs01-weu-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con la sorpresa de que apareció un mensaje de error como este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TestTerraformNoFunction 2020-08-11T22:35:11+02:00 logger.go:66: avs-hb01-hub01-shs01-weu-dns-01
    terraform_azure_test.go:52: 
            Error Trace:    terraform_azure_test.go:52
            Error:          Not equal: 
                            expected: "avs-hb01-hub01-shs01-weu-dns-01"
                            actual  : "avs-hb01-hub01-shs01-weu-01"

                            Diff:
                            --- Expected
                            +++ Actual
                            @@ -1 +1 @@
                            -avs-hb01-hub01-shs01-weu-dns-01
                            +avs-hb01-hub01-shs01-weu-01
            Test:           TestTerraformNoFunction
--- FAIL: TestTerraformNoFunction (4.74s)
TestTerraformComplete 2020-08-11T22:35:11+02:00 logger.go:66: avs-hb01-hub01-shs01-weu-dns-01
--- PASS: TestTerraformComplete (4.77s)
FAIL
exit status 1
FAIL    test    4.780s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después de revisarlo no veía error en el código, pero parecía que estuviese mezclando las variables de las dos funciones.&lt;/p&gt;

&lt;p&gt;¿Cuál era el problema? Pues resultó ser el “t.Parallel()”. Yo sé de Golang lo justo para pasar el día pero tiene toda la pinta de que el ejecutar cada paso en paralelo Terraform se vuelve un poco loco.&lt;/p&gt;

&lt;p&gt;Solución:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quitar la ejecución en paralelo
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gruntwork-io/terratest/modules/terraform"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/stretchr/testify/assert"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestTerraformComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"avs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"company"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hb01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hub01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"project"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"shs01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"weu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dns"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"num"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"avs-hb01-hub01-shs01-weu-dns-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestTerraformNoFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"avs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"company"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hb01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hub01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"project"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"shs01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"weu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"num"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"avs-hb01-hub01-shs01-weu-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Ejecutar los tests desde una misma función
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gruntwork-io/terratest/modules/terraform"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/stretchr/testify/assert"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestTerraform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;NoFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"avs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"company"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hb01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hub01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"project"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"shs01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"weu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dns"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"num"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"avs-hb01-hub01-shs01-weu-dns-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NoFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;terraformOptions&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TerraformDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"../"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;Vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"avs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"company"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hb01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hub01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"project"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"shs01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"weu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"num"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitAndApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;terraformOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"avs-hb01-hub01-shs01-weu-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Espero que os sirva de ayuda :)&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>testing</category>
      <category>go</category>
    </item>
    <item>
      <title>Desplegar Nextcloud en Docker usando Ansible</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 08:46:05 +0000</pubDate>
      <link>https://dev.to/callepuzzle/desplegar-nextcloud-en-docker-usando-ansible-5dfl</link>
      <guid>https://dev.to/callepuzzle/desplegar-nextcloud-en-docker-usando-ansible-5dfl</guid>
      <description>&lt;p&gt;Nextcloud nos permite tener nuestro propio &lt;a href="https://nextcloud.com/files/" rel="noopener noreferrer"&gt;servidor de archivos&lt;/a&gt; a lo Dropbox. Pero a mí lo que más me gusta es poder tener &lt;a href="https://nextcloud.com/groupware/" rel="noopener noreferrer"&gt;mi calendario y mis contactos&lt;/a&gt; independiente del todopoderoso Google.&lt;/p&gt;

&lt;p&gt;Vamos a ver como podemos desplegar Nextcloud en una Debian usando Docker y todo ello automatizado con Ansible.&lt;/p&gt;

&lt;p&gt;El esquema que vamos a tener es una máquina Debian con tres contenedores dentro:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;haproxy&lt;/li&gt;
&lt;li&gt;nextcloud&lt;/li&gt;
&lt;li&gt;mariadb&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fra2nzpd55nde9ixykdul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fra2nzpd55nde9ixykdul.png" alt="Nextcloud containerized"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requisitos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tener acceso root a la máquina Debian.&lt;/li&gt;
&lt;li&gt;Tener instalado Ansible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mí personalmente me gusta instalar Ansible en un virtualenv de python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;virtualenv3 venv/py3
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;venv/py3/bin/activate
&lt;span class="o"&gt;(&lt;/span&gt;py3&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;ansible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instalamos los roles necesarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;py3&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ansible-galaxy &lt;span class="nb"&gt;install &lt;/span&gt;jilgue.ansible_role_docker_nextcloud
&lt;span class="o"&gt;(&lt;/span&gt;py3&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ansible-galaxy &lt;span class="nb"&gt;install &lt;/span&gt;jilgue.ansible_role_docker_haproxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Playbook
&lt;/h2&gt;

&lt;p&gt;A continuación vamos a generar la configuración de Ansible.&lt;/p&gt;

&lt;p&gt;Necesitamos generar el inventario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;nextcloud&lt;/span&gt; &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192.168.56.20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;También la configuracion del haproxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global
    daemon
    maxconn 256

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http-in
    bind *:80
    mode http
    default_backend servers

backend servers
    server server1 nextcloud:80 maxconn 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y por último el playbook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;become_method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo&lt;/span&gt;

  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nextcloud_trusted_domains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.56.20&lt;/span&gt;
    &lt;span class="na"&gt;nextcloud_ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
    &lt;span class="na"&gt;haproxy_cfg_template_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;playbook_dir&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/"&lt;/span&gt;
    &lt;span class="na"&gt;haproxy_links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nextcloud&lt;/span&gt;
    &lt;span class="na"&gt;haproxy_ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;80:80&lt;/span&gt;

  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;nextcloud&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nextcloud'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;haproxy&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;haproxy'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y con esto tendríamos todo, al aplicar el playbook y entrar en la url &lt;a href="http://192.168.56.20" rel="noopener noreferrer"&gt;http://192.168.56.20/&lt;/a&gt; podremos ver nuestro nextcloud instalado.&lt;/p&gt;

&lt;p&gt;El usuario y contraseña por defecto son &lt;a href="https://github.com/CallePuzzle/ansible-role-docker-nextcloud/blob/master/defaults/main.yml" rel="noopener noreferrer"&gt;admin / admin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fig698ei6i7c833bsuvc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fig698ei6i7c833bsuvc1.png" alt="Nextcloud welcome"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Podéis ver la configuración de mi propio servidor en &lt;a href="https://github.com/CallePuzzle/ansible-playbook-hetzner-cloud" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ansible</category>
      <category>nextcloud</category>
    </item>
    <item>
      <title>Autenticar nginx mediante el método service-to-service en GCP</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 08:35:22 +0000</pubDate>
      <link>https://dev.to/callepuzzle/autenticar-nginx-mediante-el-metodo-service-to-service-en-gcp-2chk</link>
      <guid>https://dev.to/callepuzzle/autenticar-nginx-mediante-el-metodo-service-to-service-en-gcp-2chk</guid>
      <description>&lt;p&gt;Imaginad que estamos montado una serie de microservicios en Cloud Run donde el endpoint es un nginx y no queremos tener el resto de servicios públicos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_aWZ4im9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfvwxbwofae925pw22ls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_aWZ4im9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfvwxbwofae925pw22ls.png" alt="Cloud run diagram" width="446" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google nos provee de un método de autenticación &lt;a href="https://cloud.google.com/run/docs/authenticating/service-to-service"&gt;service-to-service&lt;/a&gt;. Perfecto, ¿cómo hacemos eso en nginx? Pues usando el módulo &lt;a href="http://nginx.org/en/docs/http/ngx_http_auth_request_module.html"&gt;ngx_http_auth_request_module&lt;/a&gt; y el &lt;a href="https://nginx.org/en/docs/njs/"&gt;njs scripting language&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El código que necesitamos en nginx es el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;js_include&lt;/span&gt; &lt;span class="nx"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="nx"&gt;default_server&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;server_name&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;auth_request&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;_oauth2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;auth_request_set&lt;/span&gt; &lt;span class="nx"&gt;$authorization&lt;/span&gt; &lt;span class="nx"&gt;$sent_http_authorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;proxy_set_header&lt;/span&gt; &lt;span class="nx"&gt;Authorization&lt;/span&gt; &lt;span class="nx"&gt;$authorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;proxy_pass&lt;/span&gt; &lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//patinando-int-api-tfezajqgva-ew.a.run.app;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/_oauth2 &lt;/span&gt;&lt;span class="err"&gt;{
&lt;/span&gt;        &lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;js_content&lt;/span&gt; &lt;span class="nx"&gt;introspectAccessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/_oauth2_send_request &lt;/span&gt;&lt;span class="err"&gt;{
&lt;/span&gt;        &lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;proxy_set_header&lt;/span&gt;  &lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Flavor&lt;/span&gt; &lt;span class="nx"&gt;Google&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;proxy_pass&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://patinando-int-api-tfezajqgva-ew.a.run.app;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;introspectAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subrequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/_oauth2_send_request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                         &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headersOut&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AUTHORIZATION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseBody&lt;/span&gt;
                         &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                         &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendHeader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                         &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                     &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                         &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                     &lt;span class="p"&gt;}&lt;/span&gt;
                 &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En nuestro caso nuestro servicio interno es &lt;em&gt;&lt;a href="https://patinando-int-api-tfezajqgva-ew.a.run.app"&gt;https://patinando-int-api-tfezajqgva-ew.a.run.app&lt;/a&gt;&lt;/em&gt; y si lanzamos un &lt;em&gt;wget&lt;/em&gt; obtenemos un bonito 403:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP request sent, awaiting response… 
 HTTP/1.1 403 Forbidden
 Date: Sun, 03 May 2020 11:36:33 GMT
 Content-Type: text/html; charset=UTF-8
 Server: Google Frontend
 Content-Length: 295
2020–05–03 11:36:33 ERROR 403: Forbidden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lo que estamos haciendo con este código es decir que toda petición se tiene que autorizar con &lt;code&gt;/_oauth2&lt;/code&gt; que es una llamada interna y contiene el código de la función en javascript.&lt;/p&gt;

&lt;p&gt;El código javascript a su vez llama a &lt;code&gt;/_oauth2_send_request&lt;/code&gt; para obtener el token.&lt;/p&gt;

&lt;p&gt;Para poder usar la cabecera que seteamos en javascript con&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r.headersOut[‘AUTHORIZATION’] = ‘Bearer ‘ + reply.responseBody
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;es necesario añadir estas líneas en el location principal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth_request_set $authorization $sent_http_authorization;
proxy_set_header Authorization $authorization;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si ahora hacemos peticiones al nginx la api nos devolverá un 200:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
 Server: nginx/1.17.10
 Date: Sun, 03 May 2020 11:44:56 GMT
 Content-Type: application/json
 Content-Length: 5436
 Connection: keep-alive
 vary: Authorization
 x-powered-by: PHP/7.4.5
 cache-control: no-cache, private
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Referencias:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nginx.com/blog/validating-oauth-2-0-access-tokens-nginx/"&gt;https://www.nginx.com/blog/validating-oauth-2-0-access-tokens-nginx/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>googlecloud</category>
      <category>nginx</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Configurar Emacs usando Doom</title>
      <dc:creator>César M. Cristóbal</dc:creator>
      <pubDate>Fri, 11 Feb 2022 08:02:07 +0000</pubDate>
      <link>https://dev.to/callepuzzle/configurar-emacs-usando-doom-315j</link>
      <guid>https://dev.to/callepuzzle/configurar-emacs-usando-doom-315j</guid>
      <description>&lt;h2&gt;
  
  
  ¿Por qué Emacs?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt; fue el primer IDE que aprendí a usar, por aquel entonces todos programábamos en la misma máquina conectándonos por ssh.&lt;/p&gt;

&lt;p&gt;Más tarde nos "modernizamos" y empezamos a usar PhpStorm. Así que cuando dejé Php de lado y empecé a programar en Terraform, Ansible y Python, opté por usar PyCharm ya que es un productor de la misma compañía y el cambio iba a ser más sencillo.&lt;/p&gt;

&lt;p&gt;Ahora bien el mundo JetBrains tiene su pega, para cada lenguaje necesitas un maldito editor distinto, y cuando empecé a hacer cosillas en Go me negué a instalar otro más.&lt;/p&gt;

&lt;p&gt;Vale, Emacs mola mucho y todo lo que tu quieras pero configurarlo es un dolor. Por eso opté por probar &lt;a href="https://github.com/hlissner/doom-emacs"&gt;Doom&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doom
&lt;/h2&gt;

&lt;p&gt;Doom es un framework para configurar Emacs.&lt;/p&gt;

&lt;p&gt;Básicamente nuestra configuración se guarda en &lt;code&gt;~/.doom.d/&lt;/code&gt; donde tenemos tres ficheros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;init.el: desde el cual activas o desactivas extensiones&lt;/li&gt;
&lt;li&gt;packages.el: desde el que puedes instalar paquetes de MELPA, ELPA o emacsmirror&lt;/li&gt;
&lt;li&gt;config.el: donde va tu configuración personal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;y ejecutando &lt;code&gt;~/.emacs.d/bin/doom sync&lt;/code&gt; se generará nuestra configuración en &lt;code&gt;~/.emacs.d/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalación
&lt;/h2&gt;

&lt;p&gt;Seguimos los pasos del &lt;a href="https://github.com/hlissner/doom-emacs/blob/develop/docs/getting_started.org#install"&gt;getting started&lt;/a&gt; y al arrancar por primera vez Emacs veremos algo como esto:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GHJxZmbv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iakiz9v3itbauzb3i9at.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GHJxZmbv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iakiz9v3itbauzb3i9at.png" alt="Primera carga de Emacs con Doom" width="800" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuración
&lt;/h2&gt;

&lt;p&gt;Después de jugar un poco me di cuenta de que había demasiados paquetes instalados que hacían demasiadas cosas que no sabía usar y me “estorbaban”.&lt;/p&gt;

&lt;p&gt;Me habían aconsejado fuertemente que probase la extensión &lt;a href="https://github.com/emacs-evil/evil"&gt;evil&lt;/a&gt; pero de momento prefiero el modo de edición de emacs “puro”. Me quedo sin galletas :(&lt;/p&gt;

&lt;p&gt;Así que comenté las siguientes extensiones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;evil +everywhere&lt;/li&gt;
&lt;li&gt;+bindings +smartparens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Además de los lenguajes que fuese a utilizar.&lt;br&gt;
Añadimos la configuración para borrar los espacios en blanco al guardar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; Hooks
(add-hook ‘before-save-hook ‘delete-trailing-whitespace)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrancamos el servidor, de manera que luego podamos abrir un fichero desde otra terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; Server
(server-start)

$ emacsclient -n .bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Añadimos keybindings que para mí son necesarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; Keybindings
(global-set-key “\M-p” ‘backward-paragraph)
(global-set-key “\M-n” ‘forward-paragraph)
(global-set-key (kbd “C-7”) ‘undo)
(global-set-key (kbd “C-x p”) ‘counsel-projectile-switch-project)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y con esto ya estaría para funcionar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autocompletado
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://company-mode.github.io/"&gt;Company&lt;/a&gt; ya viene por defecto y en principio con activar lsp sería suficiente. Aunque esta parte aún la tengo algo verde y hay cosas que no terminan de ir fino.&lt;/p&gt;

&lt;h2&gt;
  
  
  NeoTree
&lt;/h2&gt;

&lt;p&gt;Es una extensión para ver el árbol de ficheros y directorios de un proyecto. Para activarlo con su extensión de iconos hay que realizar los siguientes cambios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instalar “all-the-icons”
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(package! all-the-icons)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;descomentar “neotree” del init.el&lt;/li&gt;
&lt;li&gt;añadir la siguiente configuración:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; NeoTree
(setq neo-theme (if (display-graphic-p) ‘icons ‘arrow))
(global-set-key (kbd “C-x t”) ‘neotree-show)
(setq projectile-switch-project-action ‘neotree-projectile-action)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mi configuración al final ha quedado así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jilgue/doom-emacs-config"&gt;Ver repositorio en GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>emacs</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
