<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Giovanni Silva]]></title><description><![CDATA[Experienced programmer and software engineer in Java and Web Platform (JavaScript, HTML, CSS).
I'm also interested in Kubernetes DevOps and infrastructure]]></description><link>https://blog.gsilva.pro</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 22:50:40 GMT</lastBuildDate><atom:link href="https://blog.gsilva.pro/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Clean Architecture]]></title><description><![CDATA[This article describes a series of techniques and architectural decisions inspired by next-gen projects that use Spring Boot and clean architecture as a backbone for their backend implementation. This is based on my personal and professional experien...]]></description><link>https://blog.gsilva.pro/clean-architecture</link><guid isPermaLink="true">https://blog.gsilva.pro/clean-architecture</guid><category><![CDATA[Java]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 06 Apr 2024 16:30:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/-HIiNFXcbtQ/upload/7e22b9ec8a9ecd06efe56524e10ed5d5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article describes a series of techniques and architectural decisions inspired by next-gen projects that use Spring Boot and clean architecture as a backbone for their backend implementation. This is based on my personal and professional experience and complies with the original Spring Boot authors' recommended and canonical way of using the Spring Boot framework.</p>
<h1 id="heading-principles">Principles</h1>
<p>The technical principles that guide this document are:</p>
<ul>
<li><p>Code maintenance and evolution - We should be able to maintain and evolve a code base over time. All developers should be able to follow sound and widespread standards. The system should be isolated between technical infrastructure decisions (database, external rest services, etc.) and the business logic domain.</p>
</li>
<li><p>Cloud-native approach. The backend should be designed to operate on the cloud and take advantage of its surroundings. That topic means different things and keeps evolving. We will drill down what we mean by it in practical terms.</p>
</li>
<li><p>Testability and quality. The backend must be testable and provide a good coverage of relevant test cases. The tests should be in line with modern spring boot test approaches.</p>
</li>
<li><p>Spring Boot Native. The backend should follow and take advantage of spring boot auto configuration, containerization, tests, security, integrations, and extensions in a canonical way that is compatible with the framework's evolution without workarounds. Specific company extensions should consider how they play with the framework configuration model to extend it without compromising other features and integrations.</p>
</li>
<li><p>I suggest following the principle of conventional overconfiguration and flexibility to override and adapt solutions. That means plugins developed by teams to use in Spring Boot can be opted in and out quickly and provide ways to be configured on specific behaviors and change (automatically or not) its configuration on different environments. That aligns with the Spring Boot Native principle and creates reusable components.</p>
</li>
</ul>
<p>💡In the Spring Boot topic, I will explore how to combine AOP, Exception Handling, Meta Annotations, Conditional Beans, and Spring Boot Configuration Model to create powerful, reusable, clean, and sophisticated automation examples to plug-and-play in any Spring Boot application on the company, with minimal effort and flexibility in the hands of the consuming developers. An improvement on top of that is to distribute the configuration across multiple microservices; we will not cover that advanced scenario. However, we should be able to suggest ideas in the microservice architecture style that take advantage of all the code foundations mentioned in this document.</p>
<h1 id="heading-code-maintenance-and-evolution">Code Maintenance and Evolution</h1>
<p>To organize the code, we suggest the Clean Architecture structure in a single mono project by each microservice.</p>
<p>Clean Architecture is an architectural style created by Robert C. Martin that can be summarized in the following:</p>
<p>The clean architecture creates clear boundaries between business rules (stable and higher-level abstractions) and volatile technical details (lower-level abstractions). The main principle is the dependency rule: Source code dependencies must point only inward toward higher-level policies.</p>
<p>That means infrastructure details (databases, rest services, queues, etc..) are isolated from business logic and can be evolved, dismissed, or added in isolation. In fact, the clean code architecture should also have the following characteristics:</p>
<ul>
<li><p>Testable</p>
</li>
<li><p>Independent UI Layer</p>
</li>
<li><p>Independent of frameworks</p>
</li>
<li><p>Independent of infrastructure like databases</p>
</li>
</ul>
<p>On a high level, the structure of a spring boot project is as follows:</p>
<pre><code class="lang-plaintext">application
domain
infrastructure
presentation
</code></pre>
<p>Let's dive into each layer following a typical user request flow (from presentation to infrastructure)</p>
<h2 id="heading-the-presentation-layer">The Presentation Layer</h2>
<p>This layer will program the APIs and controllers. All external requests pass through this layer and are technology-agnostic. The most common way is through REST APIs. Still, it could be a console application, a GraphQL, GRPC, an async queue, or any other communication from the outside world to drive the application's behavior.</p>
<p>Talking about REST, there is an <strong>api/v0</strong> folder inside the presentation. Each controller is versioned starting with version v0, which means it is under development and has no obligation to be stable or retrocompatible; each contract object is versioned inside the folder. When a stable version is published, it becomes v1, and another package is created, or the v0 is renamed. The v1 can evolve if it is retro-compatible with itself. If not, a v2 folder is created, and previous versions are still maintained in accordance with the team. There is no need to version at the class level in this approach.</p>
<p>These are the main things inside the <strong>presentation</strong> layer:</p>
<pre><code class="lang-plaintext">presentation
  api
    v0
      request
        CreatePersonRequest
      response
        CreatePersonResponse
      ExampleController.java
  config
    ObjectMapperConfig.java
    SwaggerConfig.java
  AppExceptionHandler.java
</code></pre>
<p>The clean architecture states: "Source code dependencies must point only inward toward higher-level policies." In practice, we can achieve this by isolating layers. The presentation layer depends only inward (depends on the application layer) and is isolated through the use of request and response objects. The convention is to use Js as the name prefix for requests and responses, which is optional and differentiates from domain classes when the names are very similar or equal (this is common while developing). What is not optional is the fact that these kinds of objects need to be created and transformed from/to the inwork layers.</p>
<p>The transformation technique is open to debate; some use mappers, some use static methods, and others use libraries to help, like the map struct library. Each team can use what it is most comfortable with. My only recommendation is to be consistent across the same microservice code base. The mapping is also used in the infrastructure layer; we will discuss it later.</p>
<p>The presentation layer will also have custom validation, pagination, and other API-related things.</p>
<p>Spring has a powerful programming model called Aspect Orienting Programming (AOP), and Spring Boot brings it to another level using the Conditional Beans feature. We could use these features to write clean and maintainable code, cleaning up many cross-cutting aspects of the application like logging, metrics, security, and auditing, and cleaning up significantly the controllers.</p>
<h3 id="heading-exception-handling">Exception Handling</h3>
<p>Spring Boot has a canonical way of dealing with exceptions that is quite good.</p>
<p>The principle is to let the exceptions be thrown as specific runtime exceptions with the code and only catch each one in a single place. That is what the AppExceptionHandler does. Catching each one is not an accurate description, as we can catch groups and subclasses. We could also invert the logic of dependency to create a single catch and avoid having to treat each coupled with the AppExceptionHandler, making the exception treat itself (but that is just an idea open to be proved).</p>
<p>The UI will need specific codes to translate the messages and deal with errors. The <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc7807">Problems Details</a> RFC standard could be interesting to study in other to standardize the responses from errors and validations.</p>
<h2 id="heading-the-application-layer">The application layer</h2>
<p>The presentation layer will get user inputs and delegate the execution to the application layer; the application layer can be considered as all application use cases. The key here is that the application layer will orchestrate the fulfillment of a flow or need of the user (be it a human or machine interacting with the application). The application layer can use many domain services and objects to fulfill its requests in a single transaction.</p>
<p>Speaking of transactions, this layer is a good candidate in most cases for controlling transactions, which means database transactions are initiated here.</p>
<p>When the controllers in the presentation layer access the application layer, they will convert the request to a domain object and pass it to it. Let's give an example:</p>
<pre><code class="lang-plaintext">application
   ExamplePersonUseCase.java
   ExamplePersonAdapterUseCase.java
presentation
  api
    v0
      ExamplePersonSetupController.java
      response
        PersonCreatedResponse.java
      request
        NewPersonRequest.java
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequiredArgsConstruct</span>
<span class="hljs-meta">@RequestMapping(path = "/api/v0/persons")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExamplePersonSetupController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ExamplePersonUseCase examplePersonUseCase;

    <span class="hljs-meta">@PostMapping()</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> PersonCreatedResponse <span class="hljs-title">createNewPerson</span><span class="hljs-params">(<span class="hljs-meta">@Valid</span> JsNewPersonRequest newPersonRequest)</span> </span>{
        <span class="hljs-keyword">return</span> PersonCreatedResponse.of(examplePersonUseCase.createNewPerson(newPersonRequest.toDomain());
    }
}
</code></pre>
<p>The controller above is responsible for API-related tasks only (single responsibility principle).</p>
<p>The ExamplePersonUseCase is an interface with the contract between the layers:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ExamplePersonUseCase</span> </span>{
    <span class="hljs-function">Person <span class="hljs-title">createNewPerson</span><span class="hljs-params">(Person person)</span></span>;
}
</code></pre>
<p>The <strong>ExamplePersonAdapterUseCase</strong> is an implementation. The adapter is a reference to ports and adapters. The interface is a port, and the adapter implements it. It generally has only one adapter, but it could have more, especially when dealing with different databases and persistence.</p>
<p>The pseudo implementation could be something like this:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-meta">@RequiredArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExamplePersonAdaptUseCase</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ExamplePersonUseCase</span> </span>{
   <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ExamplePersonRepositoryService examplePersonRepositoryService;
   <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ExamplePersonRequisitsService examplePersonRequisitsService;


   <span class="hljs-meta">@Override</span>   
   <span class="hljs-meta">@Transactional</span>
   <span class="hljs-function"><span class="hljs-keyword">public</span> Person <span class="hljs-title">createNewPerson</span><span class="hljs-params">(Person person)</span> </span>{
      examplePersonRequisitsService.checkIfCanBeAdded(person.getPassport());
      <span class="hljs-keyword">return</span> examplePersonRepositoryService.createNewPerson(person);
   }
}
</code></pre>
<h2 id="heading-the-domain-layer">The Domain layer</h2>
<p>The domain layer represents the business model. It overlaps with the data layer representation because it is common to have a representation as a persistence object (entity or table). But it is not the same. The business layer will represent the problem and can be modeled according to the system and problem. The only hard rule that <strong>Robert C. Martin</strong> talks about is:</p>
<p><mark>The domain model should not directly depend on technology aspects.</mark></p>
<p>That means if we need to persist or read data from a database, we should depend on an interface, not an implementation. Also we should minimize the use of external libraries in this layer.</p>
<p>Some tips:</p>
<ul>
<li><p>Avoid over-normalization of the domain layer. It will make it difficult and less performant to retrieve and persist information. It can also complicate data transformation.</p>
</li>
<li><p>Create a rich model layer; do not use the domain layer only to carry data. You can reuse logic from the domain objects. For example, custom validation logic related to a single object can be added to the object itself.</p>
</li>
<li><p>Prefer immutabibility.</p>
</li>
<li><p>Remember: You still have the application layer to orchestrate multiple domain layer logic.</p>
</li>
</ul>
<p>For example:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> lombok.Value;

<span class="hljs-keyword">import</span> java.time.LocalDateTime;
<span class="hljs-keyword">import</span> java.time.temporal.ChronoUnit;
<span class="hljs-keyword">import</span> java.util.Locale;
<span class="hljs-keyword">import</span> java.util.Map;
<span class="hljs-keyword">import</span> java.util.Objects;

<span class="hljs-meta">@Value</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Map&lt;String, Integer&gt; LEGAL_AGE_IN_COUNTRIES = Map.of(
            Locale.US.getCountry(),
            <span class="hljs-number">21</span>,
            Locale.UK.getCountry(),
            <span class="hljs-number">21</span>,
            <span class="hljs-string">"BR"</span>,
            <span class="hljs-number">18</span>
    );

    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> LocalDateTime birthDay;

    <span class="hljs-keyword">private</span> Address address;

    <span class="hljs-function"><span class="hljs-keyword">private</span> Integer <span class="hljs-title">getLegalAgeInCountry</span><span class="hljs-params">(Locale.IsoCountryCode countryCode)</span> </span>{
        <span class="hljs-keyword">return</span> LEGAL_AGE_IN_COUNTRIES.get(countryCode) == <span class="hljs-keyword">null</span> ? <span class="hljs-number">0</span> : LEGAL_AGE_IN_COUNTRIES.get(countryCode);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">ageInYears</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> (<span class="hljs-keyword">int</span>) ChronoUnit.YEARS.between(birthDay, LocalDateTime.now());
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">canSignIn</span><span class="hljs-params">(Locale.IsoCountryCode countryCode)</span> </span>{
        <span class="hljs-keyword">return</span> ageInYears() &gt;= getLegalAgeInCountry(countryCode) &amp;&amp; Objects.equals(address.getCountryCode(), countryCode.name());
    }
}
</code></pre>
<p>This simple domain layer example shows that business logic related to the <strong>Person</strong>, like verifying whether it can sign in to the system, is performed in the domain object. The object is immutable (The <code>@Value</code> annotation).</p>
<p>If the domain layer exposes or consumes information, for example, loading the person from the database, it will depend on an interface. We call it a <code>RepositoryService</code><strong>.</strong> The team can treat the repository service as a generic data access that does not rely on implementation; it could be a service, a relational database, or a nonrelational database. If you want, you could explicitly separate the concept of a data repository from an external REST service; just use a different name convention, like<code>ExternalService</code>.</p>
<p>For example:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> java.util.Optional;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">PersonRepositoryService</span> </span>{

    <span class="hljs-function">Optional&lt;Person&gt; <span class="hljs-title">findById</span><span class="hljs-params">(PersonId personId)</span></span>;
    <span class="hljs-function">Person <span class="hljs-title">save</span><span class="hljs-params">(Person person)</span></span>;
    <span class="hljs-function">List&lt;Person&gt; <span class="hljs-title">findAll</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<p>Note: The service should consume domain objects and return domain objects. This means there will be a transformation in the infrastructure layer on the implementation side.</p>
<h2 id="heading-the-infrastructure-layer">The Infrastructure Layer</h2>
<p>The infrastructure layer performs every technology-related task, like database operations or external REST API</p>
<p>consumption. This layer typically implements the <code>RepositoryService</code> from the domain layer. We call this an Adapter. This concept is correlated with "Ports and Adapters" from the <a target="_blank" href="https://alistair.cockburn.us/">Alistar Cockburn</a> <a target="_blank" href="https://wiki.c2.com/?HexagonalArchitecture=">Hexagonal Architecture</a> reference article.</p>
<p>For example, this is the Spring Data JPA implementation of the <code>PersonRespositoryService</code></p>
<pre><code class="lang-java"><span class="hljs-meta">@Repository</span>
<span class="hljs-meta">@RequiredArgsConstructor</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonRepositoryAdapterImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">PersonRepositoryService</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> PersonRepository personRepository;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Optional&lt;Person&gt; <span class="hljs-title">findById</span><span class="hljs-params">(PersonId personId)</span> </span>{
        <span class="hljs-keyword">return</span> personRepository.findById(personId.id())
                .map(PersonEntity::toDomain);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Person <span class="hljs-title">save</span><span class="hljs-params">(Person person)</span> </span>{
        <span class="hljs-keyword">return</span> personRepository.save(PersonEntity.fromDomain(person)).toDomain();
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;Person&gt; <span class="hljs-title">findAll</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> personRepository.findAll()
                .stream()
                .map(PersonEntity::toDomain)
                .toList();
    }
}
</code></pre>
<p>The <strong>entity</strong> maps its contents <strong><em>from</em></strong> and <strong><em>to</em></strong> the domain layer in this example.</p>
<p>The infrastructure layer is divided into:</p>
<pre><code class="lang-plaintext">database
  entity
    AddressEntity.java
    PersonEntity.java
  repository
    PersonRepository.java
  PersonRespositoryAdapterImpl.java
</code></pre>
<p>This is technology-specific. You could have a <strong>rest</strong> folder in the infrastructure layer to communicate with REST API's</p>
<p><a target="_blank" href="https://github.com/giovannicandido/course-clean-arch">Check out this example repository</a> for a complete code setup</p>
]]></content:encoded></item><item><title><![CDATA[Criando um ambiente dev -  Parte 7]]></title><description><![CDATA[Dicas de como usar o Visual Studio Code com o WSL de forma a desenvolver nativamente no Linux com o windows sem maquina virtual.]]></description><link>https://blog.gsilva.pro/criando-um-ambiente-dev-parte-7</link><guid isPermaLink="true">https://blog.gsilva.pro/criando-um-ambiente-dev-parte-7</guid><category><![CDATA[Visual Studio Code]]></category><category><![CDATA[WSL]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 09 Mar 2024 02:12:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XmZ4GDAp9G0/upload/1672d9ead4877eecaa9b18f2b71ce3df.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dicas de como usar o Visual Studio Code com o WSL de forma a desenvolver nativamente no Linux com o windows sem maquina virtual.</p>
<div class="hn-embed-widget" id="windows-dev-env-7"></div>]]></content:encoded></item><item><title><![CDATA[Criando um ambiente de devenvolvimento Parte - 6]]></title><description><![CDATA[Vamos montar um shell personalizado no Windows e Linux]]></description><link>https://blog.gsilva.pro/criando-um-ambiente-de-devenvolvimento-parte-6</link><guid isPermaLink="true">https://blog.gsilva.pro/criando-um-ambiente-de-devenvolvimento-parte-6</guid><category><![CDATA[Windows]]></category><category><![CDATA[Developer]]></category><category><![CDATA[windows terminal]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[Bash]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 09 Mar 2024 02:10:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ylveRpZ8L1s/upload/7564ae26d11e7e4e1e7cbb466fb1f469.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Vamos montar um shell personalizado no Windows e Linux</p>
<div class="hn-embed-widget" id="windows-dev-env-6"></div>]]></content:encoded></item><item><title><![CDATA[Criando um Ambiente Dev - Parte 5]]></title><description><![CDATA[Agora vamos preparar o ambiente WSL com onde vamos rodar um shell Linux nativo de maneira integrada com windows e bem mais leve que uma máquina virtual.
Com isso podemos executar ferramentas exclusivas ou melhores no Linux enquanto mantemos nossa maq...]]></description><link>https://blog.gsilva.pro/criando-um-ambiente-dev-parte-5</link><guid isPermaLink="true">https://blog.gsilva.pro/criando-um-ambiente-dev-parte-5</guid><category><![CDATA[WSL]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 09 Mar 2024 02:08:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/842ofHC6MaI/upload/25534be2153fd24dad354f3e8d58e5b3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Agora vamos preparar o ambiente WSL com onde vamos rodar um shell Linux nativo de maneira integrada com windows e bem mais leve que uma máquina virtual.</p>
<p>Com isso podemos executar ferramentas exclusivas ou melhores no Linux enquanto mantemos nossa maquina principal com o windows.</p>
<div class="hn-embed-widget" id="windows-dev-env-5"></div>]]></content:encoded></item><item><title><![CDATA[Criando um Ambiente Dev Parte 4]]></title><description><![CDATA[Vamos focar na personalização e uso do Powershell, também apresendo o Nushell que é uma alternativa moderna porém pouco conhecida para shells no windows, linux e macosx]]></description><link>https://blog.gsilva.pro/criando-um-ambiente-dev-parte-4</link><guid isPermaLink="true">https://blog.gsilva.pro/criando-um-ambiente-dev-parte-4</guid><category><![CDATA[Windows]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[nushell]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 09 Mar 2024 02:04:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/842ofHC6MaI/upload/25534be2153fd24dad354f3e8d58e5b3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Vamos focar na personalização e uso do Powershell, também apresendo o Nushell que é uma alternativa moderna porém pouco conhecida para shells no windows, linux e macosx</p>
<div class="hn-embed-widget" id="windows-dev-env-4"></div>]]></content:encoded></item><item><title><![CDATA[Criando um Ambiente Dev - Parte 3]]></title><description><![CDATA[Nessa parte da serie vamos personalizar o Windows Terminal e instalar pacotes com os gerenciadores do windows chamados winget e scoop, demostro e dou dicas de quando uso um ou outro.]]></description><link>https://blog.gsilva.pro/criando-um-ambiente-dev-parte-3</link><guid isPermaLink="true">https://blog.gsilva.pro/criando-um-ambiente-dev-parte-3</guid><category><![CDATA[Windows]]></category><category><![CDATA[winget]]></category><category><![CDATA[Scoop]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 09 Mar 2024 02:00:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/wvJuYrM5iuw/upload/c8327c3bd0719da05d1fa5d2830bce37.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Nessa parte da serie vamos personalizar o Windows Terminal e instalar pacotes com os gerenciadores do windows chamados winget e scoop, demostro e dou dicas de quando uso um ou outro.</p>
<div class="hn-embed-widget" id="windows-dev-env-3"></div>]]></content:encoded></item><item><title><![CDATA[Criando um Ambiente de Desenvolvimento - Parte 2]]></title><description><![CDATA[Nesse video vou falar sobre terminais no windows, como o Powershell e o WSL usando o Windows Terminal e vamos ver o resultado de uma personalização do shell para ficar com informações uteis e bonitas na linha de comando.]]></description><link>https://blog.gsilva.pro/criando-um-ambiente-de-desenvolvimento-parte-2</link><guid isPermaLink="true">https://blog.gsilva.pro/criando-um-ambiente-de-desenvolvimento-parte-2</guid><category><![CDATA[terminal]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 09 Mar 2024 01:56:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/842ofHC6MaI/upload/25534be2153fd24dad354f3e8d58e5b3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Nesse video vou falar sobre terminais no windows, como o Powershell e o WSL usando o Windows Terminal e vamos ver o resultado de uma personalização do shell para ficar com informações uteis e bonitas na linha de comando.</p>
<div class="hn-embed-widget" id="windows-dev-env-2"></div>]]></content:encoded></item><item><title><![CDATA[Criando um Ambiente Dev Completo no Windows]]></title><description><![CDATA[Nessa série de videos vamos criar um ambiente de desenvolvimento completo no Windows. As ferramentas listadas e apresentadas servem para praticamente qualquer linguagem de programação como Java, Javascript, Python, Go, etc. Isso porque eu não ensino ...]]></description><link>https://blog.gsilva.pro/criando-um-ambiente-dev-completo-no-windows</link><guid isPermaLink="true">https://blog.gsilva.pro/criando-um-ambiente-dev-completo-no-windows</guid><category><![CDATA[Windows]]></category><category><![CDATA[Developer]]></category><category><![CDATA[dev]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Thu, 07 Mar 2024 11:29:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/wvJuYrM5iuw/upload/c8327c3bd0719da05d1fa5d2830bce37.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Nessa série de videos vamos criar um ambiente de desenvolvimento completo no Windows. As ferramentas listadas e apresentadas servem para praticamente qualquer linguagem de programação como Java, Javascript, Python, Go, etc. Isso porque eu não ensino a fazer um ambiente especifico, mas a usar o shell e gerenciadores de pacotes a seu favor de forma a montar qualquer ambiente (ensinar a pescar e não dar o peixe)</p>
<p>Nesse primeiro video eu dou um overview de todas as ferramentas que vamos usar, e não há somente ferramentas de programação, mas também devops, segurança, documentação, gerenciamento de projetos e nerdices.</p>
<p>Vamos lá espero que gostem 👌</p>
<div class="hn-embed-widget" id="windows-dev-env-1"></div>]]></content:encoded></item><item><title><![CDATA[WSL Slow as Hell Tip]]></title><description><![CDATA[Hi, quick tip. I encountered this behavior where WSL was very slow on pings, file edits, and other interactions. The problem is that the /etc/hosts file has an automatically added entry for the local machine but in the wrong format.
Take my wrong hos...]]></description><link>https://blog.gsilva.pro/wsl-slow-as-hell-tip</link><guid isPermaLink="true">https://blog.gsilva.pro/wsl-slow-as-hell-tip</guid><category><![CDATA[WSL]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Tue, 13 Feb 2024 19:31:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/NLSXFjl_nhc/upload/b39c53850fb40ebb2cc21f41f88a31c4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi, quick tip. I encountered this behavior where WSL was very slow on pings, file edits, and other interactions. The problem is that the <strong>/etc/hosts</strong> file has an automatically added entry for the local machine but in the wrong format.</p>
<p>Take my wrong host file as an example:</p>
<pre><code class="lang-plaintext"># This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateHosts = false
127.0.0.1       localhost
127.0.1.1       Home-Z97.       Home-Z97

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
</code></pre>
<p>My machine is called Home-Z97, the entry "Home-Z97. Home-Z97" is wrong. You need to change it to: "Home-Z97"</p>
<p>Here is the correct full file:</p>
<pre><code class="lang-plaintext"># This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateHosts = false
127.0.0.1       localhost
127.0.1.1       Home-Z97

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
</code></pre>
<p>Save this and edit the file <strong>/etc/wsl.conf.</strong> Add the <strong>generatedHosts = false</strong> flag</p>
<pre><code class="lang-ini"><span class="hljs-section">[network]</span>
<span class="hljs-attr">enerateHosts</span> = <span class="hljs-literal">false</span>
</code></pre>
<p>Restart WSL with the command in PowerShell: <code>wsl --shutdown</code></p>
<p>This is a bizarre problem, and I came up with the solution by accident.</p>
<p>This solves the problem on two different machines. I hope that helps! 😊</p>
]]></content:encoded></item><item><title><![CDATA[Trabalho final T21 Fundatec Detalhamento Parte 2]]></title><description><![CDATA[Vamos detalhar o restante do trabalho final nesse post.
Para o nossa API de estacionamento minima faltam as seguintes API's
Plano:

Criar um plano

Recarregar um plano


Tarifa

Calcular Tarifa

Entrar (entrada de veiculo)

Sair (Saída de Veículo)


...]]></description><link>https://blog.gsilva.pro/trabalho-final-t21-fundatec-detalhamento-parte-2</link><guid isPermaLink="true">https://blog.gsilva.pro/trabalho-final-t21-fundatec-detalhamento-parte-2</guid><category><![CDATA[aulas]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Wed, 01 Feb 2023 20:10:40 GMT</pubDate><content:encoded><![CDATA[<p>Vamos detalhar o restante do trabalho final nesse post.</p>
<p>Para o nossa API de estacionamento minima faltam as seguintes API's</p>
<p>Plano:</p>
<ul>
<li><p>Criar um plano</p>
</li>
<li><p>Recarregar um plano</p>
</li>
</ul>
<p>Tarifa</p>
<ul>
<li><p>Calcular Tarifa</p>
</li>
<li><p>Entrar (entrada de veiculo)</p>
</li>
<li><p>Sair (Saída de Veículo)</p>
</li>
</ul>
<h2 id="heading-criando-um-plano">Criando um plano</h2>
<p>Para criar um plano basta o id do cliente e o valor do mesmo. O json pode ser algo do tipo:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"idCliente"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">"valor"</span>: <span class="hljs-number">100</span>
}
</code></pre>
<p>Poderiamos ter um <strong>POST</strong> em /api/plano para criar um novo plano.</p>
<p>Crie o controlador de planos e faça as anotações necessária. Faça um DTO para receber os dados do post.</p>
<h2 id="heading-recarga-do-plano">Recarga do plano</h2>
<p>A regarga do plano recebe o mesmo objeto (DTO), mas escuta no path /api/plano/recarga</p>
<p>Dentro do service será necessário procurar o plano pelo id do cliente usando-se o repository, se o plano existir somar o valor passado com o valor atual e salvar o objeto plano novamente.</p>
<p>O método no repository para buscar um plano pelo id do assinante (idCliente) seria:</p>
<pre><code class="lang-java">    <span class="hljs-meta">@Query("select c from Plano c join c.assinante a where a.id = :id")</span>
    <span class="hljs-function">Plano <span class="hljs-title">findByAssinanteId</span><span class="hljs-params">(<span class="hljs-meta">@Param("id")</span> Long id)</span></span>;
</code></pre>
<h2 id="heading-calcular-uma-tarifa">Calcular uma tarifa</h2>
<p>Para calcular tarifa podemos fazer usando o id do veiculo ou o id da entrada do veiculo. Se fizermos pelo primeiro teriamos que buscar o veiculo que possui uma entrada e não possui data de saida, pois esse veiculo está então estacionado no estacionamento. Lembre-se nossas entradas e saídas são controladas pela tabela/entidade <strong>Tarifa</strong></p>
<p>Vamos ver a lógica alto nível para calcular uma tarifa pelo id do veiculo</p>
<p>O contrato (API) pode ser assim:</p>
<pre><code class="lang-java">    <span class="hljs-meta">@GetMapping(path = "/calcular")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> CalculoTarifaDTO <span class="hljs-title">calcularTarifa</span><span class="hljs-params">(
            <span class="hljs-meta">@RequestParam("idVeiculo")</span> Long idVeiculo,
            <span class="hljs-meta">@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")</span>
            <span class="hljs-meta">@RequestParam("saida")</span> LocalDateTime saida
            )</span> </span>{
        <span class="hljs-keyword">return</span> service.calcularTarifa(idVeiculo, saida);
    }
</code></pre>
<p>Note que estamos recebendo como parametro o id e a data de saída. Para data de saída estamos dizendo ao spring qual o formato esperamos na requisição, infelizmente as formas de fazer isso globalmente não funcionaram até a presenta data. Nesse caso o formato é "2023-12-24 12:02:34"</p>
<p>Todos os @RequestParam se traduzem em parametros de query no insomia:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675279733882/9934f3fe-1183-4d5a-a860-48c08637c885.png" alt class="image--center mx-auto" /></p>
<p>A lógica de negócio para calcular tarifa (feita no service) pode começar com varias validações:</p>
<ol>
<li><p>Verifica se veículo passado existe pelo seu id (findById)</p>
</li>
<li><p>Verifica se uma tarifa por tipo de veiculo está cadastrada</p>
</li>
<li><p>Busca a tarifa (entrada do veiculo) no banco e verifica se ela existe</p>
</li>
</ol>
<p>Por fim o serviço passa para a tarifa os dados necessários para o calculo e retorna o resultado: A data de saída, se o cliente é assinante e as tarifas para aquele tipo</p>
<p>As queries que o service de tarifas tem que fazer para calcular a tarifa são as seguinte:</p>
<p>Busca o veiculo por seu id (os metodos findById são herdados do JpaRepository quando se cria o repository). Atentar-se ao uso do optional que nunca é nulo, ele tem metodos para verificar se o valor está vazio e o metodo get() para pegar o item opcional (no caso veiculo)</p>
<pre><code class="lang-java">Optional&lt;Veiculo&gt; veiculo = veiculoRepository.findById(idVeiculo);
</code></pre>
<p>Busca a primeira tarifa por tipo salva no banco para um determinado tipo de veiculo (CARRO, MOTO). Note que o spring data vai gerar a query correta com base no nome do metodo</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">TarifaPorTipoRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">TarifaPorTipo</span>, <span class="hljs-title">Long</span>&gt; </span>{
    <span class="hljs-function">Optional&lt;TarifaPorTipo&gt; <span class="hljs-title">findFirstByTipoVeiculo</span><span class="hljs-params">(TipoVeiculo tipoVeiculo)</span></span>;
}
</code></pre>
<p>Busca a Tarifa (entrada do veiculo) dado o veiculo:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">TarifaRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">Tarifa</span>, <span class="hljs-title">Long</span>&gt; </span>{

    <span class="hljs-function">Tarifa <span class="hljs-title">findFirstByVeiculoAndSaidaIsNull</span><span class="hljs-params">(Veiculo idVeiculo)</span></span>;
}
</code></pre>
<p>Repare que esse metodo retorna null se não encontrar registro. A query gerada pelo spring data vai verificar a tarifa para aquele veiculo tem uma saida nula (entrou e não saiu)</p>
<p>De posse dessas informações basta passarmos os dados para a propria tarifa retornada calcular (é possivel fazer isso no service também mas vamos separar para ter um pouco de modelos ricos)</p>
<pre><code class="lang-java"><span class="hljs-keyword">return</span> tarifa.calcularTarifa(saida, cliente.isAssinante(), tarifas.get());
</code></pre>
<p>Lembre-se: A saida foi passada pelo controlador ao chamar o service, e o metodo isAssinante vem do cliente que por sua vez vem de dentro do veiculo (o jpa vai buscar o cliente com base na relação feita entre eles @ManyToOne</p>
<p>Para calcular uma tarifa verifique o codigo em <a target="_blank" href="https://github.com/giovannicandido/aulas-fundatec-estacionamento-exercicio1/blob/master/src/main/java/br/org/fundatec/lp2/estacionamento/Veiculo.java">aulas-fundatec-estacionamento-exercicio1/Veiculo.java at master · giovannicandido/aulas-fundatec-estacionamento-exercicio1 (</a><a target="_blank" href="http://github.com">github.com</a><a target="_blank" href="https://github.com/giovannicandido/aulas-fundatec-estacionamento-exercicio1/blob/master/src/main/java/br/org/fundatec/lp2/estacionamento/Veiculo.java">)</a></p>
<p>A logica é a mesma porém as tarifas agora são passadas como parametro, e o tempo é calculado conforme a data de saida (a data de entrada já temos salva).</p>
<p>O código para calcular o tempo em minutos entre duas datas é:</p>
<pre><code class="lang-java"><span class="hljs-keyword">long</span> tempoEstacionamento = ChronoUnit.MINUTES.between(entrada, saida);
</code></pre>
<p>Não se esqueça de que se ele é um assinate ele deve ter um desconto de 15%</p>
<h2 id="heading-entrar-entrada-de-um-veiculo">Entrar - Entrada de um veículo</h2>
<p>Podemos colocar em TarifaCtrl o contrato de entrada: POST - /api/tarifa/entrar</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"idVeiculo"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">"entrada"</span>: <span class="hljs-string">"2023-02-01 16:00:00"</span>,
    <span class="hljs-attr">"nomeEstacionamento"</span>: <span class="hljs-string">"estacionamento1"</span>
}
</code></pre>
<p>EntradaDTO</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EntradaDTO</span> </span>{
    <span class="hljs-keyword">private</span> Long idVeiculo;
    <span class="hljs-meta">@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")</span>
    <span class="hljs-keyword">private</span> LocalDateTime entrada;
    <span class="hljs-keyword">private</span> String nomeEstacionamento;

    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getIdVeiculo</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> idVeiculo;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setIdVeiculo</span><span class="hljs-params">(Long idVeiculo)</span> </span>{
        <span class="hljs-keyword">this</span>.idVeiculo = idVeiculo;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> LocalDateTime <span class="hljs-title">getEntrada</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> entrada;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setEntrada</span><span class="hljs-params">(LocalDateTime entrada)</span> </span>{
        <span class="hljs-keyword">this</span>.entrada = entrada;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getNomeEstacionamento</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> nomeEstacionamento;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setNomeEstacionamento</span><span class="hljs-params">(String nomeEstacionamento)</span> </span>{
        <span class="hljs-keyword">this</span>.nomeEstacionamento = nomeEstacionamento;
    }
}
</code></pre>
<p>Para fazer a entrada no serviço, podemos verificar:</p>
<ol>
<li><p>Se o veiculo existe</p>
</li>
<li><p>Se o estacionamento existe pelo nome (estacionamentoRepository.findById)</p>
</li>
<li><p>Se o veiculo já possui uma entrada pois ele não pode entrar duas vezes (método findFirstByVeiculoAndSaidaIsNull listado acima)</p>
</li>
</ol>
<p>Caso esses items passem na validação crie uma tarifa sete os dados de veiculo, entrada com a hora local (LocalDateTime.now()) e salve.</p>
<h2 id="heading-sair-saida-de-veiculo">Sair - Saida de veiculo</h2>
<p>A saida do veiculo segue o contrato: POST - /api/tarifa/saida</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"idVeiculo"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">"saida"</span>: <span class="hljs-string">"2023-02-01 17:00:00"</span>,
    <span class="hljs-attr">"valorPago"</span>: <span class="hljs-number">10.00</span>
}
</code></pre>
<p>Com SaidaDTO:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SaidaDTO</span> </span>{
    <span class="hljs-keyword">private</span> Long idVeiculo;
    <span class="hljs-meta">@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")</span>
    <span class="hljs-keyword">private</span> LocalDateTime saida;
    <span class="hljs-keyword">private</span> Double valorPago;

    <span class="hljs-function"><span class="hljs-keyword">public</span> Long <span class="hljs-title">getIdVeiculo</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> idVeiculo;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setIdVeiculo</span><span class="hljs-params">(Long idVeiculo)</span> </span>{
        <span class="hljs-keyword">this</span>.idVeiculo = idVeiculo;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> LocalDateTime <span class="hljs-title">getSaida</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> saida;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setSaida</span><span class="hljs-params">(LocalDateTime saida)</span> </span>{
        <span class="hljs-keyword">this</span>.saida = saida;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Double <span class="hljs-title">getValorPago</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> valorPago;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setValorPago</span><span class="hljs-params">(Double valorPago)</span> </span>{
        <span class="hljs-keyword">this</span>.valorPago = valorPago;
    }
}
</code></pre>
<p>Para calcular a saida podemos validar:</p>
<ol>
<li><p>Se o veiculo existe</p>
</li>
<li><p>Se existe uma entrada ou seja se o metodo <code>tarifaRepository.findFirstByVeiculoAndSaidaIsNull(veiculo.get())</code> retorna alguma tarifa</p>
</li>
</ol>
<p>Dai podemos salvar a Tarifa localizada setando-se o valor pago.</p>
<p>Até aqui está conforme o modelo de dados apresentado na sala de aula.</p>
<p>Um incremento que seria interessante é o seguinte:</p>
<p>Alterar o modelo de Tarifa para conter qual a tarifa calculada no momento da saida (para fins históricos) e se teve desconto ou não:</p>
<pre><code class="lang-java">  <span class="hljs-keyword">private</span> Double taxaCobrada;
  <span class="hljs-keyword">private</span> Boolean comDesconto;
</code></pre>
<p>Dai antes de salvar calcular a tarifa novamente e salvar, deve-se reaproveitar o metodo do service para calcular a tarifa.</p>
<p>Agora como saber se teve descontro ou não?</p>
<p>Para isso é possível retonar em calcularTarifa um DTO com o resultado e um booleano para saber se a tarifa deve o desconto.</p>
<p>Confira o arquivo API do insomia em: <a target="_blank" href="https://github.com/giovannicandido/fundatec-trabalho-final/raw/59f92a3eadf25be49ccec58a46f0955e98e474ab/api%20trabalho%20final.json">https://github.com/giovannicandido/fundatec-trabalho-final/raw/59f92a3eadf25be49ccec58a46f0955e98e474ab/api%20trabalho%20final.json</a></p>
<p>Caso o insomia não importe a API use o postman com esse arquivo: <a target="_blank" href="https://github.com/giovannicandido/fundatec-trabalho-final/raw/main/TFinal.postman_collection.json">https://github.com/giovannicandido/fundatec-trabalho-final/raw/main/TFinal.postman_collection.json</a></p>
]]></content:encoded></item><item><title><![CDATA[Tratamento Global de Erros em Spring - Básico]]></title><description><![CDATA[Nesse post rápido vou lhe ensinar como criar um tratamento de erros global no spring boot. Para um artigo avançado sobre o tema veja esse post
Basicamente precisamos criar um ControllerAdvice que irá interceptar exceções de tipos especificos, ou toda...]]></description><link>https://blog.gsilva.pro/tratamento-global-de-erros-em-spring-basico</link><guid isPermaLink="true">https://blog.gsilva.pro/tratamento-global-de-erros-em-spring-basico</guid><category><![CDATA[error handling]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Thu, 26 Jan 2023 23:18:34 GMT</pubDate><content:encoded><![CDATA[<p>Nesse post rápido vou lhe ensinar como criar um tratamento de erros global no spring boot. Para um artigo avançado sobre o tema veja <a target="_blank" href="https://blog.gsilva.pro/advance-exception-handler">esse post</a></p>
<p>Basicamente precisamos criar um ControllerAdvice que irá interceptar exceções de tipos especificos, ou todas as exceções se quiser capturar uma RuntimeException.</p>
<p>Crie o arquivo em sua camada de controladores (exemplo package controller)</p>
<pre><code class="lang-java"><span class="hljs-meta">@ControllerAdvice</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> </span>{
  <span class="hljs-comment">/**
   * todo translate message codes
   * Handles all DomainException
   * <span class="hljs-doctag">@param</span> ex the exception to handle
   * <span class="hljs-doctag">@return</span> ErroMessage
   */</span>
  <span class="hljs-meta">@ExceptionHandler(DomainException.class)</span>
  <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;ErrorResponse&gt; <span class="hljs-title">handleAllDomainException</span><span class="hljs-params">(DomainException ex)</span> </span>{
    <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
      .body(<span class="hljs-keyword">new</span> ErrorResponse(ex.getMessage()));
  }
}
</code></pre>
<p>Nesse caso estamos capturando um tipo de erro chamado DomainException, que é uma classe da aplicação (package domain):</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DomainException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">RuntimeException</span> </span>{

  <span class="hljs-meta">@Getter</span>
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Message domainMessage;

  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">DomainException</span><span class="hljs-params">(Message domainMessage)</span> </span>{
    <span class="hljs-keyword">super</span>(domainMessage.toString());
    <span class="hljs-keyword">this</span>.domainMessage = domainMessage;
  }
}
</code></pre>
<p>Nosso ErrorResponse é um objeto de resposta (DTO) que está na camada de controladores:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonCreator;
<span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonProperty;
<span class="hljs-keyword">import</span> lombok.Value;

<span class="hljs-meta">@Value</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ErrorResponse</span> </span>{
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String message;
  <span class="hljs-meta">@JsonCreator</span>
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ErrorResponse</span><span class="hljs-params">(<span class="hljs-meta">@JsonProperty("message")</span> String message)</span> </span>{
    <span class="hljs-keyword">this</span>.message = message;
  }
}
</code></pre>
<p>Note que essa classe aqui é Imutável, pois não faz sentido modificar o valor de <strong>message</strong> após criada. Para isso usamos o @JsonCreator para dizer qual é o contrutor a ser usado pelo Jackson Mapper para serializar para json. Caso contrário ele falha sem um construtor padrão.</p>
<p>Nota: O tipo de retorno ResponseEntity só funcionará em aplicações REST. Para aplicações diferentes (MVC por exemplo). Pode-se retornar um ModelAndView que irá renderizar um template html por exemplo.</p>
<p>Você pode interceptar outros tipos de erro como por exemplo DataIntegrityViolationException que é lançada quando há uma constraint no banco que não foi respeitada (unique, not null, etc..). Ou mesmo interceptar todos os Errors</p>
<p>Abaixo segue um exemplo mais completo de ControlerAdvice:</p>
<pre><code class="lang-java"><span class="hljs-meta">@ControllerAdvice</span>
    <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExceptionHandlerConfig</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ResponseEntityExceptionHandler</span> </span>{

      <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Logger logger;
      <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> PropertiesMessageProxy messageProxy;

      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ExceptionHandlerConfig</span><span class="hljs-params">(Logger logger, PropertiesMessageProxy messageProxy)</span> </span>{
        <span class="hljs-keyword">this</span>.logger = logger;
        <span class="hljs-keyword">this</span>.messageProxy = messageProxy;
      }

      <span class="hljs-comment">/**
       * Validates all Runtime exceptions, transforming to ApiError with generic message
       *
       * <span class="hljs-doctag">@param</span> e Exception
       * <span class="hljs-doctag">@return</span> ApiError for Json consumption
       */</span>
      <span class="hljs-meta">@ResponseStatus(INTERNAL_SERVER_ERROR)</span>
      <span class="hljs-meta">@ExceptionHandler(Exception.class)</span>
      <span class="hljs-meta">@ResponseBody</span>
      <span class="hljs-function"><span class="hljs-keyword">public</span> ApiError <span class="hljs-title">handleAll</span><span class="hljs-params">(Exception e)</span> </span>{
        logger.error(e.getMessage(), e);
        <span class="hljs-keyword">return</span> ApiError.fromMessage(INTERNAL_SERVER_ERROR,
            messageProxy.messageFromKey(MessageConstants.APPLICATION_EXCEPTION_GENERIC));
      }

      <span class="hljs-meta">@ResponseStatus(UNPROCESSABLE_ENTITY)</span>
      <span class="hljs-meta">@ExceptionHandler(DomainBusinessException.class)</span>
      <span class="hljs-meta">@ResponseBody</span>
      <span class="hljs-function"><span class="hljs-keyword">public</span> ApiError <span class="hljs-title">handleBusinessException</span><span class="hljs-params">(DomainBusinessException e)</span> </span>{
        <span class="hljs-keyword">if</span> (!e.getMessageConstants().isPresent()) {
          <span class="hljs-keyword">return</span> ApiError.fromException(UNPROCESSABLE_ENTITY, e);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">return</span> ApiError.fromMessage(UNPROCESSABLE_ENTITY,
              messageProxy.messageFromKey(e.getMessageConstants().get()));
        }
      }

      <span class="hljs-comment">/**
       * Handle <span class="hljs-doctag">@Valid</span> exceptions in methods with <span class="hljs-doctag">@RequestBody</span>
       *
       * <span class="hljs-doctag">@param</span> ex MethodException
       * <span class="hljs-doctag">@param</span> headers Default HttpHeaders
       * <span class="hljs-doctag">@param</span> status Status of the request
       * <span class="hljs-doctag">@param</span> request The web request
       * <span class="hljs-doctag">@return</span> ApiError for Json consumption
       */</span>
      <span class="hljs-meta">@Override</span>
      <span class="hljs-function"><span class="hljs-keyword">protected</span> ResponseEntity&lt;Object&gt; <span class="hljs-title">handleMethodArgumentNotValid</span><span class="hljs-params">(
          MethodArgumentNotValidException ex,
          HttpHeaders headers,
          HttpStatus status,
          WebRequest request)</span> </span>{

        <span class="hljs-keyword">return</span> ResponseEntity.badRequest().body(ApiError.fromMessage(HttpStatus.BAD_REQUEST,
            messageProxy.getMessageFromListErrors(ex.getBindingResult().getAllErrors())));
      }

      <span class="hljs-comment">/**
       * <span class="hljs-doctag">@return</span> ApiError for Json consumption
       */</span>
      <span class="hljs-meta">@ExceptionHandler({ConstraintViolationException.class})</span>
      <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;ApiError&gt; <span class="hljs-title">handleConstraintViolation</span><span class="hljs-params">(
          ConstraintViolationException ex)</span> </span>{
        List&lt;String&gt; errors = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();
        <span class="hljs-keyword">for</span> (ConstraintViolation&lt;?&gt; violation : ex.getConstraintViolations()) {
          errors.add(violation.getPropertyPath() + <span class="hljs-string">": "</span> + violation.getMessage());
        }

        ApiError apiError =
            ApiError.fromMessage(HttpStatus.BAD_REQUEST, errors);
        <span class="hljs-keyword">return</span> ResponseEntity.badRequest().body(apiError);
      }
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@Data</span> <span class="hljs-comment">// lombok annotation</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApiError</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Serializable</span> </span>{

      <span class="hljs-keyword">private</span> Integer statusCode;
      <span class="hljs-keyword">private</span> String error;
      <span class="hljs-keyword">private</span> List&lt;String&gt; messages;

      <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">ApiError</span><span class="hljs-params">(Integer statusCode, String error, String message)</span> </span>{
        <span class="hljs-keyword">this</span>(statusCode, error,
            Objects.nonNull(message) ? Collections.singletonList(message) : Collections.emptyList());
      }

      <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">ApiError</span><span class="hljs-params">(Integer statusCode, String error, List&lt;String&gt; messages)</span> </span>{
        <span class="hljs-keyword">this</span>.statusCode = statusCode;
        <span class="hljs-keyword">this</span>.error = error;
        <span class="hljs-keyword">this</span>.messages = messages;
      }

      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ApiError</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">this</span>.messages = Collections.emptyList();
      }

      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ApiError <span class="hljs-title">fromException</span><span class="hljs-params">(HttpStatus httpStatus, Exception exception)</span> </span>{
        <span class="hljs-keyword">return</span> fromException(httpStatus, exception, httpStatus.getReasonPhrase());
      }

      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ApiError <span class="hljs-title">fromException</span><span class="hljs-params">(HttpStatus httpStatus, Exception exception,
          String errorIdentifier)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ApiError(httpStatus.value(), errorIdentifier, exception.getMessage());
      }

      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ApiError <span class="hljs-title">fromMessage</span><span class="hljs-params">(HttpStatus httpStatus, String message)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ApiError(httpStatus.value(), httpStatus.getReasonPhrase(), message);
      }

      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ApiError <span class="hljs-title">fromMessage</span><span class="hljs-params">(HttpStatus httpStatus, List&lt;String&gt; message)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ApiError(httpStatus.value(), httpStatus.getReasonPhrase(), message);
      }
</code></pre>
<p>Não cobrimos:</p>
<ul>
<li><p>Internacionalização e codigos de erro</p>
</li>
<li><p>Hierarquia bem definida de Exceptions</p>
</li>
</ul>
<p>É isso espero que este post tenha sido útil.</p>
]]></content:encoded></item><item><title><![CDATA[Trabalho Final T21 Fundatec Detalhamento Parte 1]]></title><description><![CDATA[Nesse post vamos detalhar alguns aspectos do trabalho final requirido na disciplina de LPII para fundatec de Porto Alegre, no curso técnico em informática.
Esse post visa facilitar a elaboração do trabalho criando-se uma documentação que detalhe aspe...]]></description><link>https://blog.gsilva.pro/trabalho-final-t21-fundatec-detalhamento-parte-1</link><guid isPermaLink="true">https://blog.gsilva.pro/trabalho-final-t21-fundatec-detalhamento-parte-1</guid><category><![CDATA[aula]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Wed, 11 Jan 2023 22:02:19 GMT</pubDate><content:encoded><![CDATA[<p>Nesse post vamos detalhar alguns aspectos do trabalho final requirido na disciplina de LPII para fundatec de Porto Alegre, no curso técnico em informática.</p>
<p>Esse post visa facilitar a elaboração do trabalho criando-se uma documentação que detalhe aspectos do desenvolvimento que serão reforçados em sala de aula.</p>
<p><mark>Esse post não tem o intuito de ser um guia completo e substituto para as aulas</mark></p>
<p>Vamos começar com a base feita em sala de aula disponível em <a target="_blank" href="https://github.com/giovannicandido/fundatec-trabalho-final">giovannicandido/fundatec-trabalho-final (</a><a target="_blank" href="http://github.com">github.com</a><a target="_blank" href="https://github.com/giovannicandido/fundatec-trabalho-final">)</a></p>
<p>Nessa base temos 3 modelos do nosso banco: Cliente, Endereco, Plano</p>
<p>Esses constituem o cadastro básico de clientes.</p>
<p>Falta: Tarifa, Veiculo, TarifaPorTipo, Estacionamento e TipoVeiculo</p>
<p>Vamos ver como persistir o cadastro básico de clientes primeiro. Para os demais itens teremos uma mistura de CRUD com logica de negócio, pois a tarifa é calculada.</p>
<p>Nossa logica para persistir esses itens pode ser feita de duas formas:</p>
<ol>
<li><p>Primeiro se cria um endereço, depois se cria o cliente passando o id do endereço</p>
</li>
<li><p>Persiste o cliente com o objeto endereço</p>
</li>
</ol>
<p>Na primeira abordagem um frontend teria um cadastro de endereços separado do de cliente, na segunda o cadastro do endereço pode ser feito junto ao cliente. Já que o endereço tem uma relação <strong>UmParaUm</strong> com cliente qualquer uma das abordagens é correta, mas a segunda tem a caracteristica de acoplar o ciclo de vida do endereço ao do cliente, ou seja, quando atualizarmos um cliente temos que atualizar seu endereço (se não usarmos atualização parcial de objeto)</p>
<p>Vamos abordar a primeira opção.</p>
<p>Primeiro criamos o endereço. É a operação mais simples já que não envolve mais de uma entidade:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping(path = "/endereco")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EnderecoCtrl</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> EnderecoService service;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">EnderecoCtrl</span><span class="hljs-params">(EnderecoService service)</span> </span>{
        <span class="hljs-keyword">this</span>.service = service;
    }

    <span class="hljs-meta">@PostMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity <span class="hljs-title">create</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> Endereco endereco)</span> </span>{
        service.create(endereco);
        <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.CREATED).build();
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EnderecoService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">CrudService</span>&lt;<span class="hljs-title">Endereco</span>&gt; </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> EnderecoRepository enderecoRepository;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">EnderecoService</span><span class="hljs-params">(EnderecoRepository enderecoRepository)</span> </span>{
        <span class="hljs-keyword">this</span>.enderecoRepository = enderecoRepository;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Endereco <span class="hljs-title">create</span><span class="hljs-params">(Endereco entity)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.enderecoRepository.save(entity);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Endereco <span class="hljs-title">findById</span><span class="hljs-params">(Long idEndereco)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.enderecoRepository.findById(idEndereco).orElse(<span class="hljs-keyword">null</span>);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Endereco <span class="hljs-title">update</span><span class="hljs-params">(Endereco entity)</span> </span>{
        <span class="hljs-keyword">if</span> (entity.getId() == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Entidade precisa de um id para atualizar"</span>);
        }
        <span class="hljs-keyword">return</span> enderecoRepository.save(entity);
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">EnderecoRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">Endereco</span>, <span class="hljs-title">Long</span>&gt; </span>{
}
</code></pre>
<p>Vemos que o service de Endereço apenas salva a entidade endereço, usando-se o repository. Por enquanto não usamos DTO passamos diretamente a entidade entre as camadas.</p>
<p>O Controller também é bem simples.</p>
<p>Agora para persistir um cliente é necessário 3 momentos:</p>
<ol>
<li><p>Procurar o id do endereço passado no banco de dados. Caso não exista temos que parar o fluxo retornando alguma informação a quem chamou a API</p>
</li>
<li><p>Persistir o cliente</p>
</li>
<li><p>Atualizar o endereço com o ID do cliente.</p>
</li>
</ol>
<p>Note que o Service e Repository para Cliente é basicamente identico ao de Endereço:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ClienteRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">Cliente</span>, <span class="hljs-title">Long</span>&gt; </span>{
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ClienteService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">CrudService</span>&lt;<span class="hljs-title">Cliente</span>&gt; </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ClienteRepository repository;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ClienteService</span><span class="hljs-params">(ClienteRepository repository)</span> </span>{
        <span class="hljs-keyword">this</span>.repository = repository;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Cliente <span class="hljs-title">create</span><span class="hljs-params">(Cliente entity)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.repository.save(entity);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Cliente <span class="hljs-title">findById</span><span class="hljs-params">(Long idEndereco)</span> </span>{
        <span class="hljs-keyword">return</span> repository.getById(idEndereco);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Cliente <span class="hljs-title">update</span><span class="hljs-params">(Cliente entity)</span> </span>{
        <span class="hljs-keyword">if</span> (entity.getId() == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Entidade precisa de um id para atualizar"</span>);
        }
        <span class="hljs-keyword">return</span> repository.save(entity);
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-meta">@RequestMapping("/cliente")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ClienteCtrl</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ClienteService service;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> EnderecoService enderecoService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ClienteCtrl</span><span class="hljs-params">(ClienteService service, EnderecoService enderecoService)</span> </span>{
        <span class="hljs-keyword">this</span>.service = service;
        <span class="hljs-keyword">this</span>.enderecoService = enderecoService;
    }

    <span class="hljs-meta">@PostMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity <span class="hljs-title">create</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> ClienteDTO clienteDTO)</span> </span>{
        Endereco endereco = enderecoService.findById(clienteDTO.getIdEndereco());
        <span class="hljs-keyword">if</span> (endereco == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(<span class="hljs-string">"Não foi encontrado o endereco de id "</span> + clienteDTO.getIdEndereco());
        }

        Cliente cliente = <span class="hljs-keyword">new</span> Cliente();
        cliente.setCpf(clienteDTO.getCpf());
        cliente.setEndereco(endereco);
        <span class="hljs-comment">// todo setar plano</span>

        service.create(cliente);

        <span class="hljs-comment">// Atualiza endereco apontando para cliente</span>
        endereco.setCliente(cliente);
        enderecoService.update(endereco);

        <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.CREATED).build();

    }
}
</code></pre>
<p>A logica descrita nos passos anteriores foi colocada no controlador. É uma boa ideia adicioná-la ao service, o service de Cliente trabalharia com o DTO ao inves da entidade, então o generic mudaria.</p>
<p>Note que para que o id do cliente seja associado ao endereço é necessário atualizar a entidade endereço também.</p>
<p>Para atualizar um cliente a logica é bem parecida com o criar um cliente. Bastando primeiro procurar o cliente pelo seu id no banco, setar as informações e salvar o cliente.</p>
<pre><code class="lang-java"> <span class="hljs-comment">/**
     * Atualizar um cliente no banco de dados
     * <span class="hljs-doctag">@param</span> clienteDTO Informações a atualizar
     * <span class="hljs-doctag">@param</span> id Id do cliente a ser atualizado passado na URL
     * <span class="hljs-doctag">@return</span> OK se atualizado com sucesso
     */</span>
    <span class="hljs-meta">@PutMapping("/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity <span class="hljs-title">update</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> ClienteDTO clienteDTO,
                                 <span class="hljs-meta">@PathVariable("id")</span> Long id)</span> </span>{
        Endereco endereco = enderecoService.findById(clienteDTO.getIdEndereco());
        <span class="hljs-keyword">if</span> (endereco == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(<span class="hljs-string">"Não foi encontrado o endereco de id "</span> + clienteDTO.getIdEndereco());
        }

        Cliente cliente = service.findById(id);

        <span class="hljs-keyword">if</span> (cliente == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(<span class="hljs-string">"Não foi encontrado o cliente de id "</span> + id);
        }
        <span class="hljs-comment">// atualiza informações</span>
        cliente.setCpf(clienteDTO.getCpf());

        <span class="hljs-comment">// atualiza no banco</span>
        service.update(cliente);
        <span class="hljs-comment">// atualiza o endereço</span>
        endereco.setCliente(cliente);
        enderecoService.update(endereco);
        <span class="hljs-keyword">return</span> ResponseEntity.status(HttpStatus.OK).build();
    }
</code></pre>
<p>Não será detalhado o codigo para o delete nem para o update do endereço, mas seguem a mesma lógica acima. (Lembre-se o delete busca pelo id na URL)</p>
<h1 id="heading-entidade-plano">Entidade Plano</h1>
<p>A entidade plano possui uma api ligeiramente diferente das vistas até agora pois:</p>
<ol>
<li><p>Um plano só existe com um cliente associado</p>
</li>
<li><p>Podemos considerar que um plano não pode ser deletado pois ele possui créditos que seriam perdidos</p>
</li>
<li><p>Trocar o assinate do plano NÃO deve ser possível, então atualizar o plano também não faz sentido.</p>
</li>
<li><p>O plano pode ser recarregado (um de nossos requisitos)</p>
</li>
</ol>
<p>Com base nesses requisitos podemos modelar a API do plano da seguinte forma:</p>
<p>Criar um plano novo com uma carga inicial:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673312123377/8244e96b-777e-44eb-ad46-87002e723902.png" alt class="image--center mx-auto" /></p>
<p>Recarrega um plano adicionando-se um valor ao mesmo:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673312176401/2a4f7b00-5bb7-4eab-b1f6-d229a6b4178e.png" alt class="image--center mx-auto" /></p>
<p>Obtem informações de um plano de um cliente. O id passado no final da URL é o id do cliente:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673312231963/3a7fc7eb-de46-42e5-8932-541e03fbaa4f.png" alt class="image--center mx-auto" /></p>
<p>Com base nas informações já passadas é possível criar essas 3 API</p>
<p>Notas:</p>
<ol>
<li><p>Ao criar um novo plano valide se já não existe um para o cliente, caso contrario vai atualizar o valor e o cliente perderá créditos.</p>
</li>
<li><p>O mesmo DTO para recarga pode ser usado para criação de novo plano</p>
</li>
<li><p>Você não precisa atualizar o cliente com o plano igual feito ao salvar um endereço no cliente, pois está alterando o plano que possui a foreing key para o cliente.</p>
</li>
<li><p>Nas 3 API você precisará de um método no repository para retornar o plano dado o id do cliente. O método que faz essa query está abaixo em duas opções:</p>
</li>
</ol>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">PlanoRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">Plano</span>, <span class="hljs-title">Long</span>&gt; </span>{
    <span class="hljs-function">Plano <span class="hljs-title">findByAssinante</span><span class="hljs-params">(Cliente assinante)</span></span>;
    <span class="hljs-meta">@Query("select c from Plano c join c.assinante a where a.id = :id")</span>
    <span class="hljs-function">Plano <span class="hljs-title">findByAssinanteId</span><span class="hljs-params">(<span class="hljs-meta">@Param("id")</span> Long id)</span></span>;
}
</code></pre>
<p>Na primeira opção se passa o cliente como parametro e o spring vai criar a query com base no nome, ou seja um where com filtro <strong>by Assinante</strong> sendo Assinante o nome da propriedade na entidade.</p>
<p>Na segunda opção ao invez de passar o cliente completo, passamos o id e criamos uma query customizada com a anotação <code>@Query</code> que filtra os planos cujo assinante tem o id passado como parametro. O parametro é o <code>:id</code> que foi explicitado pela anotação <code>@Param</code></p>
<p>Importante: Se você retornar a entidade Plano diretamente no controlador vai ter o seguinte erro:</p>
<pre><code class="lang-java">java.lang.StackOverflowError: <span class="hljs-keyword">null</span>
    at java.base/java.lang.ClassLoader.defineClass1(Native Method) ~[na:na]
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:<span class="hljs-number">1017</span>) ~[na:na]
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:<span class="hljs-number">174</span>) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:<span class="hljs-number">800</span>) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:<span class="hljs-number">698</span>) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:<span class="hljs-number">621</span>) ~[na:na]
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:<span class="hljs-number">579</span>) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:<span class="hljs-number">178</span>) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:<span class="hljs-number">522</span>) ~[na:na]
    at com.fasterxml.jackson.databind.JsonMappingException.prependPath(JsonMappingException.java:<span class="hljs-number">445</span>) ~[jackson-databind-<span class="hljs-number">2.13</span>.<span class="hljs-number">4.2</span>.jar:<span class="hljs-number">2.13</span>.<span class="hljs-number">4.2</span>]
</code></pre>
<p>Isso acontece porque repare que o Plano possui um Cliente, e um Cliente possui um plano (bidirecional). O Jackson (biblioteca JSON) vai tentar carregar o cliente do plano de id 1 por exemplo, quando ele carregar o cliente pelo hibernate, ele vai ver que tem um plano dentro do cliente e vai tentar carregá-lo novamente, assim achando o mesmo plano que por sua vez carrega o cliente novamente, e assim sucessivamente até a memória estourar.</p>
<p>Para solucionar o problema a melhor maneira é retornar um DTO com os dados. Exemplo:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> PlanoDTO <span class="hljs-title">findByClienteId</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> Long id)</span> </span>{
        Plano plano = planoRepository.findByAssinanteId(id);
        <span class="hljs-keyword">if</span> (plano == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
        }
        PlanoDTO dto = <span class="hljs-keyword">new</span> PlanoDTO();
        dto.setValor(plano.getValor());
        ClienteDTO clienteDTO = <span class="hljs-keyword">new</span> ClienteDTO();
        clienteDTO.setCpf(plano.getAssinante().getCpf());
        clienteDTO.setIdEndereco(plano.getAssinante().getEndereco().getId());
        dto.setCliente(clienteDTO);
        <span class="hljs-keyword">return</span> dto;
    }
</code></pre>
<p>Repare que retornamos a seguinte informação:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"valor"</span>: <span class="hljs-number">10.0</span>,
    <span class="hljs-attr">"cliente"</span>: {
        <span class="hljs-attr">"cpf"</span>: <span class="hljs-string">"011100003"</span>,
        <span class="hljs-attr">"idEndereco"</span>: <span class="hljs-number">1</span>
    }
}
</code></pre>
<p>Mas a pesquisa foi no banco 3 vezes:</p>
<pre><code class="lang-bash">Hibernate: select plano0_.id as id1_2_, plano0_.cliente_id as cliente_3_2_, plano0_.valor as valor2_2_ from plano plano0_ inner join cliente cliente1_ on plano0_.cliente_id=cliente1_.id <span class="hljs-built_in">where</span> cliente1_.id=?
Hibernate: select cliente0_.id as id1_0_0_, cliente0_.cpf as cpf2_0_0_, endereco1_.id as id1_1_1_, endereco1_.bairro as bairro2_1_1_, endereco1_.cep as cep3_1_1_, endereco1_.cidade as cidade4_1_1_, endereco1_.cliente_endereco as cliente_8_1_1_, endereco1_.estado as estado5_1_1_, endereco1_.numero as numero6_1_1_, endereco1_.rua as rua7_1_1_, plano2_.id as id1_2_2_, plano2_.cliente_id as cliente_3_2_2_, plano2_.valor as valor2_2_2_ from cliente cliente0_ left outer join endereco endereco1_ on cliente0_.id=endereco1_.cliente_endereco left outer join plano plano2_ on cliente0_.id=plano2_.cliente_id <span class="hljs-built_in">where</span> cliente0_.id=?
Hibernate: select plano0_.id as id1_2_2_, plano0_.cliente_id as cliente_3_2_2_, plano0_.valor as valor2_2_2_, cliente1_.id as id1_0_0_, cliente1_.cpf as cpf2_0_0_, endereco2_.id as id1_1_1_, endereco2_.bairro as bairro2_1_1_, endereco2_.cep as cep3_1_1_, endereco2_.cidade as cidade4_1_1_, endereco2_.cliente_endereco as cliente_8_1_1_, endereco2_.estado as estado5_1_1_, endereco2_.numero as numero6_1_1_, endereco2_.rua as rua7_1_1_ from plano plano0_ inner join cliente cliente1_ on plano0_.cliente_id=cliente1_.id left outer join endereco endereco2_ on cliente1_.id=endereco2_.cliente_endereco <span class="hljs-built_in">where</span> plano0_.cliente_id=?
</code></pre>
<p>Isso acontece porque pesquisamos a entidade Plano diretamente, e as associações Cliente e Endereço são pesquisadas juntamente, pois esse é o padrão do <code>@OneToOne</code></p>
<p>Se quisermos otimizar essa pesquisa podemos fazer o seguinte:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">PlanoRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">Plano</span>, <span class="hljs-title">Long</span>&gt; </span>{
    <span class="hljs-function">Plano <span class="hljs-title">findByAssinante</span><span class="hljs-params">(Cliente cliente)</span></span>;
    <span class="hljs-meta">@Query("select c from Plano c join c.assinante a where a.id = :id")</span>
    <span class="hljs-function">Plano <span class="hljs-title">findByAssinanteId</span><span class="hljs-params">(<span class="hljs-meta">@Param("id")</span> Long id)</span></span>;

    <span class="hljs-meta">@Query("select new br.org.fundatec.tfinal.tfinal.dto.PlanoDTO(c.valor, a.cpf, a.endereco.id) " +
            "from Plano c join c.assinante a where a.id = :id")</span>
    <span class="hljs-function">PlanoDTO <span class="hljs-title">findPlanoDTOByAssinanteId</span><span class="hljs-params">(<span class="hljs-meta">@Param("id")</span> Long id)</span></span>;
}
</code></pre>
<p>Reparem no seguinte: A query retorna um <strong>new criando um dto e usando o construtor do mesmo.</strong> Dessa forma o select sabe o que seleciona e cria uma query especifica para ele.</p>
<p>No caso um select apenas é feito no banco de dados:</p>
<pre><code class="lang-java">Hibernate: select plano0_.valor as col_0_0_, cliente1_.cpf as col_1_0_, endereco2_.id as col_2_0_ from plano plano0_ inner join cliente cliente1_ on plano0_.cliente_id=cliente1_.id cross join endereco endereco2_ where cliente1_.id=endereco2_.cliente_endereco and cliente1_.id=?
</code></pre>
<p>Isso é muito mais performático.</p>
<p>Restante das classes:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PlanoDTO</span> </span>{
    <span class="hljs-keyword">private</span> Double valor;
    <span class="hljs-keyword">private</span> ClienteDTO cliente;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PlanoDTO</span><span class="hljs-params">()</span> </span>{

    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PlanoDTO</span><span class="hljs-params">(Double valor, String clientCPF, Long clienteEnderecoId)</span> </span>{
        <span class="hljs-keyword">this</span>.valor = valor;
        <span class="hljs-keyword">this</span>.cliente = <span class="hljs-keyword">new</span> ClienteDTO(clientCPF, clienteEnderecoId);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Double <span class="hljs-title">getValor</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> valor;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setValor</span><span class="hljs-params">(Double valor)</span> </span>{
        <span class="hljs-keyword">this</span>.valor = valor;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> ClienteDTO <span class="hljs-title">getCliente</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> cliente;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setCliente</span><span class="hljs-params">(ClienteDTO cliente)</span> </span>{
        <span class="hljs-keyword">this</span>.cliente = cliente;
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> PlanoDTO <span class="hljs-title">findByClienteId</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> Long id)</span> </span>{
<span class="hljs-comment">//        Plano plano = planoRepository.findByAssinanteId(id);</span>
<span class="hljs-comment">//        if (plano == null) {</span>
<span class="hljs-comment">//            return null;</span>
<span class="hljs-comment">//        }</span>
<span class="hljs-comment">//        PlanoDTO dto = new PlanoDTO();</span>
<span class="hljs-comment">//        dto.setValor(plano.getValor());</span>
<span class="hljs-comment">//        ClienteDTO clienteDTO = new ClienteDTO();</span>
<span class="hljs-comment">//        clienteDTO.setCpf(plano.getAssinante().getCpf());</span>
<span class="hljs-comment">//        clienteDTO.setIdEndereco(plano.getAssinante().getEndereco().getId());</span>
<span class="hljs-comment">//        dto.setCliente(clienteDTO);</span>
        <span class="hljs-keyword">return</span> planoRepository.findPlanoDTOByAssinanteId(id);
    }
</code></pre>
<p>Com isso nós temos 4 requisitos do trabalho modelados:</p>
<ol>
<li><p>O sistema deve permitir o cadastro, alteração e consultas nos dados de um assinante;</p>
</li>
<li><p>O cliente pode possuir apenas um plano em vigor</p>
</li>
<li><p>O cliente deve possuir um endereço</p>
</li>
<li><p>O sistema deve permitir que o assinante faça recarga do valor do seu crédito;</p>
<p> O requisito: "O endereço da pessoa deve estar completo". É fácil de fazer basta colocar todos os dados de endereço com exceção de complemento como obrigatórios. Isso pode ser feito no banco de dados e na validação do controlador</p>
</li>
</ol>
<p><strong>A api do trabalho final está no git repository</strong></p>
<p>Num próximo artigo vamos modelar as outras entidades e falar de outros requisitos da aplicação</p>
]]></content:encoded></item><item><title><![CDATA[Roteiro para aula de introdução ao banco de dados - Fundatec]]></title><description><![CDATA[Nesse artigo vamos abordar uma introdução para prática de acesso a dados usando Spring Data JPA e um banco de dados mysql.
Em uma das aulas nós refatoramos o código do controlador para separá-lo em camadas. O código está disponível em https://github....]]></description><link>https://blog.gsilva.pro/roteiro-para-aula-de-introducao-ao-banco-de-dados-fundatec</link><guid isPermaLink="true">https://blog.gsilva.pro/roteiro-para-aula-de-introducao-ao-banco-de-dados-fundatec</guid><category><![CDATA[Spring Data Jpa]]></category><category><![CDATA[jpa]]></category><category><![CDATA[MariaDB]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sun, 27 Nov 2022 23:30:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/-RBuQ2PK_L8/upload/v1669576874188/-jGJxpoAE.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Nesse artigo vamos abordar uma introdução para prática de acesso a dados usando Spring Data JPA e um banco de dados mysql.</p>
<p>Em uma das aulas nós refatoramos o código do controlador para separá-lo em camadas. O código está disponível em https://github.com/giovannicandido/aulas-fundatec-spring-rest-introducao/tree/aula-09</p>
<p>Nesse roteiro, vamos refatorar a camada de repository para persistir uma pessoa no banco de dados mysql. Vou assumir que os conceitos principais de bancos de dados já foram abordados (tabelas, selects, inserts, updates, filters, primary key, foreing key e joins)</p>
<p>Após ler esse roteiro você terá:</p>
<ol>
<li>Mysql Instalado</li>
<li>Criado um banco de dados</li>
<li>Configurado o spring boot para ter suporte ao banco mysql e para conectar no banco criado</li>
<li>Refatorado o model e o repository de pessoa para ter os dados no banco</li>
<li>Testado a API</li>
</ol>
<p>Vamos começar.</p>
<h1 id="heading-instalando-o-mysqlmariadb">Instalando o mysql/mariadb</h1>
<p>Há diversas formas de instalar o mysql, a melhor delas seria usando docker pois ele não somente permite usar o mysql mas milhares de outros serviços linux de maneira simples, além de ser a maneira mais profisional para rodar aplicações em ambientes de dev, homologação e produção.</p>
<p>Por isso vou abordar a instalação por ele, no entando vou deixar o link para instalação tradicional nas plataformas Windows e Linux, se a instalação por docker não der certo tente uma delas.</p>
<p>Nos laboratórios das aulas o mysql já vai estar instalado nas máquinas.</p>
<p>Também vou deixar links para se aprofundar no docker com mais tempo pois é um assunto mais extenso que vale apena o estudo, eu vou abordar apenas o necessário para ter o servidor mysql rodando que é o objetivo desse artigo.</p>
<h2 id="heading-instalando-o-docker">Instalando o docker</h2>
<p>Primeiramente precisamos do docker, mas o que é o docker?</p>
<p>O docker é um software que gerencia o que chamados de containers. Um conteiner é um processo de sistema isolado por meio de recursos do kernel do Linux. Esse processo possui sua rede virtual, sistema de arquivos isolado e uma imagem.</p>
<p>A imagem é um mecanismo que cria uma "foto" da instalação do software e de suas dependências, a imagem é imutável e formada por camadas que se assemelham a um commit do git.</p>
<p>Quando o docker roda um container, ele baixa a imagem de um registro e cria um processo através dela. A imagem serve então para distribuir o container.</p>
<p>O docker possui um registro central onde diversas imagens podem ser encontradas: o https://hub.docker.com</p>
<p>Um container se assemelha a uma máquina virtual (VM), porém é bem mais leve pois não possui a camada de sistema operacional nem toda emulação de hardware de uma VM.</p>
<p>Para uma definição mais detalhada de container acesse https://esr.rnp.br/administracao-de-sistemas/containers-docker-como-utilizar/</p>
<p>O docker é um dos runtimes para containers (temos também o containerd e o CRI-O) e foi o primeiro a popularizar essa tecnologia e ainda hoje é o mais popular.</p>
<p>É uma tecnologia que nasceu em cima de recursos do kernel do Linux, e um container compartilha o kernel do sistema host principal. No entando é possível instalar em Windows e MacOS através de VM's, mas não se preocupe você não precisa instalar uma máquina virtual com Linux temos uma alternativa muito mais simples.</p>
<h3 id="heading-instalacao-do-docker-no-windows">Instalação do docker no Windows</h3>
<p>Acesse: https://www.docker.com/products/docker-desktop/
Baixe o instalador e siga as instruções de instalação. É necessário estar em uma versão atual do Windows 10 ou 11
Deixe marcado a opção "use WSL2 instead of Hyper-V"
Após a instalação ele vai iniciar com o sistema e vai estar no dock a direita com um icone que se assemelha a uma baleia.
Para testar a instalação abra um terminal (de preferencia windows terminal com powershell) e digite:</p>
<pre><code>docker version
</code></pre><h3 id="heading-instalacao-no-linux">Instalação no Linux</h3>
<p>Vai depender da distribuição para o ubuntu siga as instruções em:</p>
<p>https://docs.docker.com/engine/install/ubuntu/</p>
<p>Nota: Use a docker engine e não o docker desktop para linux porque o docker desktop cria um VM, o docker engine roda nativamente e é mais performatico.</p>
<h2 id="heading-criacao-do-container-com-mysql">Criação do container com Mysql</h2>
<p>Com o docker instalado é muito simples rodar um container mysql. Para isso basta um comando:</p>
<pre><code>docker run -d --restart always --name mariadb -p <span class="hljs-number">3306</span>:<span class="hljs-number">3306</span> --env MARIADB_ROOT_PASSWORD=<span class="hljs-number">1234</span> mariadb:latest
</code></pre><p>Explicando o comando acima:</p>
<ul>
<li>docker run: Cria um container novo</li>
<li>-d : Roda em background, ou seja libera a linha de comando</li>
<li>--restart always : Diz ao docker que sempre deve iniciar o container com o systema, com isso nos não precisamos fazer um "docker start" para iniciar o container na mão</li>
<li>--name mariadb : dá um nome para o container, se deixar sem nome depois só podemos referenciar o container por um id gerado automaticamente</li>
<li>-p 3306:3306 : O container possui uma rede virtual e por padrão essa rede é inacessível para o host, somente entre os containers, então dizemos que a porta 3306 do host é mapeada para porta 3306 do container, assim acessando localhost 3306 acessamos o container</li>
<li>--env : Configura variáveis de ambiente para o container. Nesse caso criamos uma variável que diz qual a senha de root do mysql</li>
<li>mariadb:latest : mariadb é o nome da imagem que estamos rodando, latest é a versão. O docker interpreta latest como a ultima versão publicada. Na verdade o que está depois do : é uma tag, com isso podemos instalar versões específicas se quisermos.</li>
</ul>
<p>Aqui vale uma distinção: O mariadb é o fork opensource do mysql. O mysql foi comprado pela oracle e de lá para cá ele existe em uma versão free e comercial, fizeram um fork dele para mantê-lo opensource.</p>
<p>Essas informações podem ser vistas no docker hub: https://hub.docker.com/_/mariadb</p>
<p>O que vai acontecer:</p>
<p>O docker vai baixar a imagem do mariadb e vai instanciar um container.</p>
<p>Poderemos acessar o mysql como de constume, por exemplo via linha de comando:</p>
<pre><code>mysql -h localhost -u root -p
</code></pre><p>Uma  coisa deve ser dita: O container mariadb armazena os seus dados em um sistema de arquivo interno e temporário. Caso o container seja removido os dados são perdidos. Um container é inicialmente projetado para ser efêmoro, isso é, morrer e não carregar seus dados consigo. A idéia é que ele possa ser instanciado instantaneamente em outra máquina. Abordagens são necessárias para manter o estado (dados) de um container. Uma delas é o mapeamente de volume</p>
<p>Se você fizer um</p>
<pre><code>docker stop mariadb
docker rm mariadb
</code></pre><p>Todos os dados serão perdidos.</p>
<h2 id="heading-acessando-o-mysql">Acessando o mysql</h2>
<p>Há diversas formas. Eu uso a nativa do Intellij mas ela é paga. Recomendo o dbeaver pois nele pode-se conectar a diversos tipos de bancos (como o postgresql) e está disponível para todas as plataformas (Linux, Mac e Windows)</p>
<p>Baixe e instale-o em: https://dbeaver.io/download/</p>
<p>Escolha o mariadb como base de dados:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669339759453/fhgISRbEC.png" alt="image.png" /></p>
<p>Preencha os dados de host (localhost) porta (3306) usuário (root) e senha (1234)</p>
<p>Expanda o servidor, clique com o botão direito em cima de <strong>Databases</strong> e crie uma nova base de dados com nome de <strong>lpII</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669346105560/FTIelvnJ1.png" alt="image.png" /></p>
<p>A partir daqui você pode criar tabelas, fazer inserts entre outros mas vamos configurar o Spring para criar as tabelas para agente.</p>
<h1 id="heading-refatorando-a-aplicacao-para-usar-banco-de-dados">Refatorando a aplicação para usar banco de dados</h1>
<h2 id="heading-configurando-a-aplicacao-para-conectar-ao-banco">Configurando a Aplicação para Conectar ao Banco</h2>
<p>Vamos configurar a aplicação para usar o spring data jpa como framework de acesso a dados.</p>
<p>Edite o arquivo <strong>pom.xml</strong> na raiz do projeto e adicione as seguintes dependencias:</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-jpa<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.mariadb.jdbc<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mariadb-java-client<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.1.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<p>O starter JPA é a depedencia para trabalhar com dados. O <em>mariadb-java-client</em> é o driver do banco que vamos conectar.</p>
<p>Dê um refresh no maven do projeto</p>
<p>Agora edite o arquivo <strong>src/main/resources/application.properties</strong></p>
<p>Configure o spring para conectar no banco com as propriedades:</p>
<pre><code>spring.datasource.url=jdbc:mariadb:<span class="hljs-comment">//localhost/lpII</span>
spring.datasource.username=root
spring.datasource.password=<span class="hljs-number">1234</span>
</code></pre><p>Rode o projeto. Se o spring não apresentar erros a conexão com o banco foi estabelecida. Exemplo:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669571348867/wnZZgCJjQ.png" alt="image.png" /></p>
<p>Vamos configurar o hibernate JPA para gerar as tabelas com base nos modelos para agente: adicione a linha abaixo no <strong>application.properties</strong></p>
<pre><code>spring.jpa.hibernate.ddl-auto=update
</code></pre><h2 id="heading-atualizando-o-modelo-de-dados">Atualizando o modelo de dados</h2>
<p>Agora precisamos de uma tabela. Edite o arquivo <strong>model/Pessoa</strong> e adicione as minimas anotações:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669571600046/wDGHUpvbj.png" alt="image.png" /></p>
<p>O restante da classe deixe como está. Rode o projeto novamente e veja se uma tabela <em>pessoa</em> foi criada</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669571675368/mtyLPXidC.png" alt="image.png" /></p>
<p>Explicando as anotações:</p>
<ul>
<li>@ Entity - Colocada na classe diz que ela é uma entidade, ou seja, uma tabela no banco de dados</li>
<li>@ Id - Toda entidade deve possui um ID que a identifica unicamente na tabela (primary key), O id então será a propriedade id do tipo Long
@ GeneratedValue - Instrui o hibernate a gerar essa propriedade usando alguma estratégia (auto increment, sequence, etc). No caso o strategy é IDENTITY que significa que o banco vai gerar para agente um numero incremental.</li>
</ul>
<p>Para o restante das propriedades o hibernate vai assumir o padrão.</p>
<ul>
<li>Para cada propriedade ele vai criar uma coluna correspondente, com o tipo correspondente. Exemplo: <strong>String</strong> é mapeado para <strong>varchar(255)</strong> e <strong>LocalDate</strong> é mapeado para <strong>date</strong></li>
<li>O nome da coluna é o nome da propriedade</li>
</ul>
<p>Podemos sobrescrever os default com outras anotações. Por exemplo para alterar o tamanho de uma coluna ou o seu nome:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669572170288/taVvXqdPi.png" alt="image.png" /></p>
<p>A propriedade <strong>spring.data.hibernate.ddl-auto=update</strong> tentará atualizar o banco, no entando há casos em que ela não consegue. Altere temporariamente o valor para <strong>create-drop</strong> reinicie o spring e a tabela será criada novamente com a coluna nome em <strong>varchar(60)</strong></p>
<h2 id="heading-modificando-o-repository">Modificando o repository</h2>
<p>Vamos alterar o repository de pessoa para que as operações sejam feitas por ele. Temos duas opções:</p>
<ol>
<li>Criar um repository com a API do spring data e quebrar o contrato com o service, fazendo com que tenhamos que refatorar todos os metodos do service</li>
<li>Implementar uma interface com o contrato entre service e repository</li>
</ol>
<p>Vamos fazer o segundo por ser mais elaborado. Mas note que em um projeto que começe com spring data isso não seria necessário pois já se utilizaria as API dos mesmo</p>
<p>Renomeie a classe PessoaRepository para PessoaRepositoryImpl</p>
<p>No intellij clique com o botão direito em qualquer metodo da classe e vá em refactor &gt; extract interface.</p>
<p>De o nome da interface de PessoaRepository</p>
<p>Selecione todos os metodos publicos</p>
<p>Crie e quando for perguntado se deseja analisar e fazer o replace das partes que usam PessoaRepositoryImpl para PessoaRepository diga sim.</p>
<p>Verifique se a classe PessoaService agora depende de PessoaRepository (interface)
Verifique se a classe teste está ok. Atualmente ela não faz nada então os testes devem compilar e passar (vamos abordar testes de spring em outro momento)</p>
<p>Crie uma nova interface PessoaRepositorySpring</p>
<p>Extenda de JpaRepository e passe os generic Pessoa e Long:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669573714664/YR1oshj15.png" alt="image.png" /></p>
<p>Esse seria um repository padrão do spring. Explicando:</p>
<p>Ao estender de JpaRepository herdamos todos os metodos necessários para se trabalhar com o banco. O spring irá injetar essa classe e criar a implementação para agente. O primeiro generic, é o tipo de Entidade que o repository trabalha, no caso <strong>Pessoa</strong>, o segundo generic é o tipo do ID que pessoa tem, no caso <strong>Long</strong></p>
<p>Temos duas opções para não quebrar a API. </p>
<p>A primeira opção seria criar uma nova classe de implementação de PessoaRepository e usar o PessoaRepositorySpring dentro dela:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669575035766/mye-hIKVE.png" alt="image.png" /></p>
<p>Mas isso gera uma classe a mais sem necessidade. A segunda opção é que podemos fazer com que a propria interface PessoaRepositorySpring implemente os metodos usando o recurso de default implementation das  interfaces java:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669574139568/GQhH4-7PX.png" alt="image.png" /></p>
<p>Porém há um problema. O método findById da interface PessoaRepository não é compativel e não pode ser sobrescrito pelo metodo findById de JpaRepository fazendo com que haja um problema de compilação. Para isso a melhor forma é fazer com que o retorno de PessoaRepository seja compatível com ele, ou renomear o método.</p>
<p>Vamos renomear o metodo para que não quebre o retorno esperado pelo Service (lembre-se estamos fazendo isso tudo para não refatorar o service ou seja não alterar a camada superior com uma implementação nova da camada repository)</p>
<p>Faça um refactoring renomentado PessoaRepository <strong>findById</strong> para <strong>findWithId</strong> e adicione a implementação em PessoaRepositorySpring:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669574561212/gE8NE2r7u.png" alt="image.png" /></p>
<p>Tente rodar a aplicação.</p>
<p>Temos um erro de compilação que diz: "reference to deleteById is ambiguous"
Isso acontece porque tanto o PessoaRepositorySpring, quanto PessoaRepository declaram deleteById e PessoaRepositorySpring extende PessoaRepository.</p>
<p>Para resolver renomeie o metodo em PessoaRepository e implemente em PessoaRepositorySpring:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669574866703/iP6_-jsvG.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669574881135/fU5x1oHm9.png" alt="image.png" /></p>
<p>Tente rodar a aplicação:</p>
<p>Spring vai falhar com uma mensagem: </p>
<blockquote>
<p>Parameter 0 of constructor in br.org.fundatec.lp2.demorest.service.PessoaService required a single bean, but 3 were found:</p>
<ul>
<li>pessoaRepositoryImpl: defined in file [C:\Users\giova\Projects\personal\aulas\fundatec\aula09\demo-rest\target\classes\br\org\fundatec\lp2\demorest\repository\PessoaRepositoryImpl.class]</li>
<li>pessoaRepositoryImplSring: defined in file [C:\Users\giova\Projects\personal\aulas\fundatec\aula09\demo-rest\target\classes\br\org\fundatec\lp2\demorest\repository\PessoaRepositoryImplSring.class]</li>
<li>pessoaRepositorySpring: defined in br.org.fundatec.lp2.demorest.repository.PessoaRepositorySpring defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration</li>
</ul>
</blockquote>
<p>Isso acontece porque agora temos 3 implementação possíveis para PessoaRepository e o spring não sabe qual injetar.</p>
<p>A forma mais simples de resolver isso é setar uma das implementações como <strong>@Primary</strong> isso vai fazer com que o spring use-a.</p>
<p>Primeiramente vamos setar como <strong>@Primary</strong> nossa antiga implementação de repository em memória com listas:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669575362226/k29WGX4-v.png" alt="image.png" /></p>
<p>Reinicie e teste inserir alguma coisa usando o Insomnia REST</p>
<p>Verifique que a API retorna itens usando o GET pessoas, mas no dbeaver não vai haver registros</p>
<p>No dbeaver vá em <strong>Sql Editor &gt; Open SQL Console</strong></p>
<p>Digite a query:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> pessoa;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669575583434/CpxUNVAAV.png" alt="image.png" /></p>
<p>Agora troque a implementação para uma das classes de repository que usam o Spring JPA (PessoaRepositoryImplSpring ou PessoaRepositorySpring), adicionando o <strong>@Primary</strong> em outra classe (não se esqueça de removê-lo de PessoaRepositoryImpl)</p>
<p>Repita o teste e rode a query novamente. Deve encontrar os registros:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669575747366/1XS6XAc3h.png" alt="image.png" /></p>
<h1 id="heading-conclusao">Conclusão</h1>
<p>Nos refatoramos o repository para salvar no banco de dados os dados de pessoa. No final acabamos com 5 classes no repository:</p>
<ul>
<li>PessoaIndex - Auxiliar para trabalhar com a lista em memória</li>
<li>PessoaRepository - Nossa interface principal da qual PessoaService depende</li>
<li>PessoaRepositorySpring - Interface que usa o spring para ir no banco de dados. Essa é a classe principal para conexão com o banco</li>
<li>PessoaRepositoryImpl - Nossa implementação de lista em memória</li>
<li>PessoaRepositoryImplSpring - Uma implementação intermediaria como alternativa para manter a compatibilidade com a interface do service.</li>
</ul>
<p>Na próxima aula vamos criar um novo endpoint, ele nele vamos ter apenas uma interface para repository, a interface principal do spring.</p>
<p>O código para essa aula está disponível em: https://github.com/giovannicandido/aulas-fundatec-spring-rest-introducao/tree/aula-11</p>
<h1 id="heading-referencias">Referências</h1>
<p>Seguem alguns links para estudarem os assuntos:</p>
<p>Instalação do mariadb:</p>
<p>https://mariadb.org/download</p>
<p>Documentação sobre Docker:</p>
<p>https://docs.docker.com/get-started/
https://blog.geekhunter.com.br/docker-na-pratica-como-construir-uma-aplicacao/</p>
<p>Documentação Spring Data e JPA</p>
<p>https://www.baeldung.com/the-persistence-layer-with-spring-data-jpa
https://www.treinaweb.com.br/blog/iniciando-com-spring-data-jpa</p>
<p>https://www.alura.com.br/apostila-java-web/uma-introducao-pratica-ao-jpa-com-hibernate
https://www.devmedia.com.br/jpa-e-hibernate-acessando-dados-em-aplicacoes-java/32711
Vários Tutoriais:
https://www.javaguides.net/p/jpa-tutorial-java-persistence-api.html</p>
<p>Apresentação sobre JPA
https://1drv.ms/p/s!AoHvV-Rb6N9Qg9YV8286A29SOLJnbw?e=51hdJh</p>
]]></content:encoded></item><item><title><![CDATA[Getting started with Ansible]]></title><description><![CDATA[In this post we will talk about Ansible, an automation tool for provision of IT infrastructure like configuration of Linux VM's, Kubernetes clusters and more. We will focus on Linux configuration and provision in this post.
We will not cover ansible ...]]></description><link>https://blog.gsilva.pro/getting-started-with-ansible</link><guid isPermaLink="true">https://blog.gsilva.pro/getting-started-with-ansible</guid><category><![CDATA[ansible]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Tue, 23 Aug 2022 19:08:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/UK78i6vK3sc/upload/v1661281778518/gidyntl4B.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post we will talk about Ansible, an automation tool for provision of IT infrastructure like configuration of Linux VM's, Kubernetes clusters and more. We will focus on Linux configuration and provision in this post.</p>
<p>We will not cover ansible automation platform, we will talk about ansible core and modules, because the automation platform is a enterprise tool that offer many features to control and execute ansible scripts in any environment you have. It's good for big teams when you need more control and contribution over the playbooks you run. Also is a way more complex software.</p>
<p>The official ansible site is a little confuse to starters, because it show documentation primary focus on the Red Hat Ansible Automation Platform, and as I mention this software is focused on enterprise customers. The ansible core is opensource and free to use and is much more simpler to start with.</p>
<h2 id="heading-core-concepts">Core Concepts</h2>
<p>How ansible automate the tasks necessary for Linux provision? To answer that question lets talk about ansible core</p>
<p>The ansible core is collection of CLI tools, the Ansible language and a architectural framework that allows extensions though ansible collections.</p>
<p>The ansible language is based on YAML to express the desired state of the machines you are controlling. This YAML is interpreted by python under the covers that implements the functionality with build in modules or extended modules installed with ansible-galaxy.</p>
<p>Ansible connects to targets through SSH and use an agentless approach to function. This has advantages over agent systems because you don't have to deal with daemons installed in the machines, this leads to a more straightforward approach, ansible only requires SSH and python installed in the target machine, which most Linux distros bring by default. The disadvantages of this approach that I can bring to table are a less performant operation, because ansible has to connect to each host, the parallelization is somewhat limited but possible (check ansible serial command and <a target="_blank" href="https://blog.crisp.se/2018/01/27/maxwenzin/how-to-run-ansible-tasks-in-parallel">this</a> post for parallel tasks.</p>
<p>To understand ansible as a minimum we need to cover this topics:</p>
<ul>
<li>The ansible inventory</li>
<li>Ad Hoc Commands</li>
<li>Ansible Playbooks and Tasks</li>
<li>Modules and Collections</li>
<li>Handlers</li>
<li>Roles and the recommend layout for playbooks</li>
</ul>
<p>We will talk about then later.</p>
<h2 id="heading-installing-ansible">Installing Ansible</h2>
<p>Ansible works best on Linux host, Windows is not supported. If you are in Windows I recommend installing in you WSL2 distro.</p>
<p>You need an minimal python 3.8 installed. In ubuntu 20.04.4 is already installed</p>
<p>I use arch Linux so I will cover how to install in arch as well.</p>
<p>In ubuntu do not install the package ansible, because it's old.</p>
<p>Run the follow commands:</p>
<pre><code class="lang-plain">python3 -m pip -V
# If pip is not installed
sudo apt install python3-pip
python3 -m pip install --user ansible # install ansible for your current user
</code></pre>
<p>Ansible will be installed for your current user. You need <strong>~/.local/bin</strong> on your PATH. To do this edit ~/.bashrc (or ~/.zshrc in zshell) and add the line:</p>
<pre><code class="lang-plain">export PATH=~/.local/bin:$PATH
</code></pre>
<p>Reopen your terminal to take effect and test the installation with:</p>
<pre><code class="lang-plain">ansible --version
</code></pre>
<p>On Arch Linux the community package is up to date so you can just:</p>
<pre><code class="lang-plain">sudo pacman -Sy ansible
</code></pre>
<h3 id="heading-upgrading-ansible">Upgrading ansible</h3>
<p>To upgrade in ubuntu run the command:</p>
<pre><code class="lang-plain">python3 -m pip install --upgrade --user ansible
</code></pre>
<p>In Arch Linux:</p>
<pre><code class="lang-plain">sudo pacman -Syu ansible
</code></pre>
<h1 id="heading-getting-started">Getting Started</h1>
<h3 id="heading-building-an-inventory">Building an Inventory</h3>
<p>An inventory is where you target your Linux machines and groups then so you can run commands and playbooks on it. It provides system information like username and network ip address reducing the number of command line options you need to specify.</p>
<p>The managed node (a remote system or host, that ansible controls) can be specified in inventories or in the file /etc/ansible/hosts. For a example of the hosts file see the file below:</p>
<pre><code class="lang-plain">[myvirtualmachines]
192.0.2.50
192.0.2.51
192.0.2.52
</code></pre>
<p>It creates a group of machines called myvirtualmachines and adds 3 ip address to it.</p>
<p>When connecting to the host you need to specify the -u option for the username (if is different than current logged user). To check the host run the command:</p>
<pre><code class="lang-plain">ansible all --list-hosts
</code></pre>
<p>You should see the output:</p>
<blockquote>
<p>hosts (1):<br />192.0.2.50<br />192.0.2.51<br />192.0.2.52</p>
</blockquote>
<p>To ping in the host and check connection run the command:</p>
<pre><code class="lang-plain">ansible all -m ping
</code></pre>
<p>Lets see how to use inventories, which are a more powerful way to catalog your managed nodes.</p>
<p>Create a folder <strong>ansible-test</strong> to sum up the files for this article, all files will be created inside it.</p>
<p>Create a file named <strong>inventory.yaml</strong> (you can use ini files as well)</p>
<p>Add the following example</p>
<pre><code class="lang-yaml"><span class="hljs-attr">virtualmachines:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm01:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.4</span>
    <span class="hljs-attr">vm02:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.5</span>
    <span class="hljs-attr">vm03:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.6</span>
</code></pre>
<p>Verify your inventory:</p>
<pre><code class="lang-plain">ansible-inventory -i inventory.yaml --list
</code></pre>
<p>I'm using a few options here because I'm using hyper-v and vagrant to spin up the machines. Lets explain them:</p>
<ul>
<li>ansible_ssh_private_key_file: Use a private key file that has generated by vagrant in machine creation. This is optional</li>
<li>ansible_user: the user that ansible will connect to</li>
<li>ansible_host: the ip address of the VM</li>
</ul>
<p>Lets ping your inventory but first you need some Ubuntu server Linux machines, spin around some in a virtualized environment and make sure you can connect with SSH. If you want use Hyper-v and want overcome some problems with it, check my blog post about <a target="_blank" href="https://blog.gsilva.pro/vagrant-hyper-v-and-ansible-for-disposable-infrastructure-environments">Hyper-V, Vagrant and Ansible</a></p>
<p>SSH will check host key on first connection effectively stopping the connection on first because we need to say 'yes' to each host. To avoid that. Add the following to your <strong>/etc/ansible/ansible.cfg</strong> file:</p>
<pre><code class="lang-plain">[ssh_connection]
ssh_args = -o StrictHostKeyChecking=accept-new
</code></pre>
<p>Now we can:</p>
<pre><code class="lang-plain">ansible virtualmachines -m ping -i inventory.yaml
</code></pre>
<p>We receive something like this as response:</p>
<blockquote>
<p>vm03 | SUCCESS =&gt; {<br />"ansible_facts": {<br />"discovered_interpreter_python": "/usr/bin/python3"<br />},<br />"changed": false,<br />"ping": "pong"<br />}<br />vm02 | SUCCESS =&gt; {<br />"ansible_facts": {<br />"discovered_interpreter_python": "/usr/bin/python3"<br />},<br />"changed": false,<br />"ping": "pong"<br />}<br />vm01 | SUCCESS =&gt; {<br />"ansible_facts": {<br />"discovered_interpreter_python": "/usr/bin/python3"<br />},<br />"changed": false,<br />"ping": "pong"<br />}</p>
</blockquote>
<p>Your communication is set up. Lets see how we can run ad-hoc commands:</p>
<h3 id="heading-running-ad-hoc-commands">Running Ad-Hoc Commands</h3>
<p>Ad hoc commands is a way to run just a few commands in the target hosts. We can use that to for example, update all host packages or to check the contents of a file in all hosts. This are not as powerfull as playbooks but are usefull.</p>
<p>Lets get started by checking the contents of a file in all machines:</p>
<p>Run the command;</p>
<pre><code class="lang-plain">ansible virtualmachines -m shell -a "cat /etc/resolv.conf" -i inventory.yaml
</code></pre>
<p>You should see the output:</p>
<blockquote>
<p>vm02 | CHANGED | rc=0 &gt;&gt;<br /># This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).<br /># Do not edit.  </p>
<h1 id="heading-ia"> </h1>
<p># This file might be symlinked as /etc/resolv.conf. If you're looking at<br /># /etc/resolv.conf and seeing this text, you have followed the symlink.  </p>
<h1 id="heading-ia"> </h1>
<p># This is a dynamic resolv.conf file for connecting local clients to the<br /># internal DNS stub resolver of systemd-resolved. This file lists all<br /># configured search domains.  </p>
<h1 id="heading-ia"> </h1>
<p># Run "resolvectl status" to see details about the uplink DNS servers<br /># currently in use.  </p>
<h1 id="heading-ia"> </h1>
<p># Third party programs should typically not access this file directly, but only<br /># through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a<br /># different way, replace this symlink by a static file or a different symlink.  </p>
<h1 id="heading-ia"> </h1>
<p># See man:systemd-resolved.service(8) for details about the supported modes of<br /># operation for /etc/resolv.conf.  </p>
<p>nameserver 127.0.0.53<br />options edns0 trust-ad<br />search .<br />vm01 | CHANGED | rc=0 &gt;&gt;<br /># This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).<br /># Do not edit.  </p>
<h1 id="heading-ia"> </h1>
<p># This file might be symlinked as /etc/resolv.conf. If you're looking at<br /># /etc/resolv.conf and seeing this text, you have followed the symlink.  </p>
<h1 id="heading-ia"> </h1>
<p># This is a dynamic resolv.conf file for connecting local clients to the<br /># internal DNS stub resolver of systemd-resolved. This file lists all<br /># configured search domains.  </p>
<h1 id="heading-ia"> </h1>
<p># Run "resolvectl status" to see details about the uplink DNS servers<br /># currently in use.  </p>
<h1 id="heading-ia"> </h1>
<p># Third party programs should typically not access this file directly, but only<br /># through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a<br /># different way, replace this symlink by a static file or a different symlink.  </p>
<h1 id="heading-ia"> </h1>
<p># See man:systemd-resolved.service(8) for details about the supported modes of<br /># operation for /etc/resolv.conf.  </p>
<p>nameserver 127.0.0.53<br />options edns0 trust-ad<br />search .<br />vm03 | CHANGED | rc=0 &gt;&gt;<br /># This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).<br /># Do not edit.  </p>
<h1 id="heading-ia"> </h1>
<p># This file might be symlinked as /etc/resolv.conf. If you're looking at<br /># /etc/resolv.conf and seeing this text, you have followed the symlink.  </p>
<h1 id="heading-ia"> </h1>
<p># This is a dynamic resolv.conf file for connecting local clients to the<br /># internal DNS stub resolver of systemd-resolved. This file lists all<br /># configured search domains.  </p>
<h1 id="heading-ia"> </h1>
<p># Run "resolvectl status" to see details about the uplink DNS servers<br /># currently in use.  </p>
<h1 id="heading-ia"> </h1>
<p># Third party programs should typically not access this file directly, but only<br /># through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a<br /># different way, replace this symlink by a static file or a different symlink.  </p>
<h1 id="heading-ia"> </h1>
<p># See man:systemd-resolved.service(8) for details about the supported modes of<br /># operation for /etc/resolv.conf.  </p>
<p>nameserver 127.0.0.53<br />options edns0 trust-ad<br />search .</p>
</blockquote>
<p>Which shows the output of the command for each machine.</p>
<p>Now lets update all packages in the hosts:</p>
<pre><code class="lang-plain">ansible virtualmachines -m shell -a "apt update &amp;&amp; apt upgrade -y" --become -i inventory.yaml
</code></pre>
<p>We use the <em>--become</em> option to run the command as root. We also need to add -y to the apt upgrade because we do not has access to a interactive shell so we say yes to everything.</p>
<p>This command takes a few time to execute, and the result is the output of the apt command. If you add <em>--ask-become-pass</em> or <em>-K</em>, Ansible will prompt your for the password to use for privilege escalation.</p>
<p>Let me explain the options of this command:</p>
<ul>
<li>virtualmachines: The first parameter is a selector for the machines in inventory. In this example we are selectiong all machine in group "virtualmachines".</li>
<li>-m: selects the module that ansible will execute. Ansible is a righly modular tools and even the core is modular. In this case we are selecting the "shell" module that executes commands in the target machine.</li>
<li>-a: Module parameters. We provide de command for the module shell</li>
<li>--become: Run the module as root using privilege escalation (sudo)</li>
<li>-i: Select the inventory</li>
</ul>
<p>We use the generic shell module to run a command. But we can use any module, in this case the use of a package module like <strong>yum</strong> or <strong>apt</strong> is more powerfull to install a specific package because is indempontent (it will not run if not needed by checking the package state.</p>
<p>To install a package use the command:</p>
<pre><code class="lang-plain">ansible virtualmachines -m ansible.builtin.apt -a "name=httpd state=present"
</code></pre>
<p>This command will ensure the package name httpd is installed. If you want to make sure is updated use:</p>
<pre><code class="lang-plain">ansible virtualmachines -m ansible.builtin.apt -a "name=httpd state=latest"
</code></pre>
<p>Another good use case for ad hoc command is to manage files using the built in <strong>file</strong> and <strong>copy</strong> module. File module allows changing owernership and permissions on files, or create directories:</p>
<pre><code class="lang-plain"># Copy a file from host to target
ansible virtualmachines  -m ansible.builtin.copy -a "src=/etc/hosts dest=/tmp/hosts"
# change permission of file
ansible virtualmachines -m ansible.builtin.file -a "dest=/srv/foo/a.txt mode=600"
ansible virtualmachines -m ansible.builtin.file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"
# creates a directory
ansible virtualmachines -m ansible.builtin.file -a "dest=/path/to/c mode=755 owner=mdehaan group=mdehaan state=directory"
# deletes a file or directory
ansible virtualmachines -m ansible.builtin.file -a "dest=/path/to/c state=absent"
</code></pre>
<p>Ansible also has the built in module <strong>user</strong> to manage users and groups. <a target="_blank" href="https://docs.ansible.com/ansible-core/2.13/collections/ansible/builtin/user_module.html#user-module">Check it's documentation</a></p>
<p>Another useful thing to do in ad hoc commands is to manage services using the <strong>service</strong> module. Examples:</p>
<p>Ensure a service is started on all webservers:</p>
<pre><code class="lang-plain">$ ansible webservers -m ansible.builtin.service -a "name=httpd state=started"
</code></pre>
<p>Alternatively, restart a service on all webservers:</p>
<pre><code class="lang-plain">$ ansible webservers -m ansible.builtin.service -a "name=httpd state=restarted"
</code></pre>
<p>Ensure a service is stopped:</p>
<pre><code class="lang-plain">$ ansible webservers -m ansible.builtin.service -a "name=httpd state=stopped"
</code></pre>
<h2 id="heading-selecting-targets">Selecting Targets</h2>
<p>There is a few ways you could select targets in the inventory. You can use a pattern to do that:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Description</td><td>Pattern(s)</td><td>Targets</td></tr>
</thead>
<tbody>
<tr>
<td>All hosts</td><td>all (or *)</td><td></td></tr>
<tr>
<td>One host</td><td>host1</td><td></td></tr>
<tr>
<td>Multiple hosts</td><td>host1:host2 (or host1,host2)</td><td></td></tr>
<tr>
<td>One group</td><td>webservers</td><td></td></tr>
<tr>
<td>Multiple groups</td><td>webservers:dbservers</td><td>all hosts in webservers plus all hosts in dbservers</td></tr>
<tr>
<td>Excluding groups</td><td>webservers:!atlanta</td><td>all hosts in webservers except those in atlanta</td></tr>
<tr>
<td>Intersection of groups</td><td>webservers:&amp;staging</td><td>any hosts in webservers that are also in staging</td></tr>
</tbody>
</table>
</div><p>You can also combine this expressions. For example <code>webservers:dbservers:&amp;staging:!phoenix</code></p>
<p>There is a wildcard option for FQDNS or IP address, as long as the hosts are named in your inventory by FQDN or IP address:</p>
<pre><code class="lang-plain">192.0.*
*.example.com
*.com
</code></pre>
<p>You can mix wildcard patterns and groups at the same time:</p>
<pre><code class="lang-plain">one*.com:dbservers
</code></pre>
<h2 id="heading-limitations-of-patterns">Limitations of patterns</h2>
<p>Patterns depend on inventory. If a host or group is not listed in your inventory, you cannot use a pattern to target it. If your pattern includes an IP address or hostname that does not appear in your inventory, you will see an error like this:</p>
<pre><code class="lang-plain">[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: Could not match supplied host pattern, ignoring: *.not_in_inventory.com
</code></pre>
<p>Your pattern must match your inventory syntax. If you define a host as an <a target="_blank" href="https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html#inventory-aliases">alias</a>:</p>
<pre><code class="lang-plain">atlanta:
  host1:
    http_port: 80
    maxRequestsPerChild: 808
    host: 127.0.0.2
</code></pre>
<p>you must use the alias in your pattern. In the example above, you must use <code>host1</code> in your pattern. If you use the IP address, you will once again get the error:</p>
<pre><code class="lang-plain">[WARNING]: Could not match supplied host pattern, ignoring: 127.0.0.2
</code></pre>
<h2 id="heading-using-regexes-in-patterns">Using regexes in patterns</h2>
<p>You can specify a pattern as a regular expression by starting the pattern with <code>~</code>:</p>
<pre><code class="lang-plain">~(web|db).*\.example\.com
</code></pre>
<h2 id="heading-patterns-and-ad-hoc-commands">Patterns and ad-hoc commands</h2>
<p>You can change the behavior of the patterns defined in ad-hoc commands using command-line options. You can also limit the hosts you target on a particular run with the <code>--limit</code> flag.</p>
<ul>
<li>Limit to one host</li>
</ul>
<pre><code class="lang-plain">$ ansible -m [module] -a "[module options]" --limit "host1"
</code></pre>
<ul>
<li>Limit to multiple hosts</li>
</ul>
<pre><code class="lang-plain">$ ansible -m [module] -a "[module options]" --limit "host1,host2"
</code></pre>
<ul>
<li>Negated limit. Note that single quotes MUST be used to prevent bash interpolation.</li>
</ul>
<pre><code class="lang-plain">$ ansible -m [module] -a "[module options]" --limit 'all:!host1'
</code></pre>
<ul>
<li>Limit to host group</li>
</ul>
<pre><code class="lang-plain">$ ansible -m [module] -a "[module options]" --limit 'group1'
</code></pre>
<p>For more information about selection pattern check the docs: <a target="_blank" href="https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html">https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html</a></p>
<h2 id="heading-ansible-playbooks-and-tasks">Ansible Playbooks and Tasks</h2>
<p>This is where the fun starts, a playbook is a repeatable, re-usable configuration management and multi-machine deployment system. Is a good idea to version control your playbooks so you can backup and share with others.</p>
<p>They are expressed as YAML format with a easy syntax. The playbook runs from top to bottom. Within each play, tasks also run in order from top to bottom. Playbooks can also include each other and orchestrate multiple machine deployments, one for webservers another for databases and other for network infrastructure, and so on. As a minimum each play defines two things:</p>
<ul>
<li>the managed nodes to target, using the pattern we saw early</li>
<li>at least one task to execute</li>
</ul>
<p>Many Ansible modules have idempotency built in (but not all of them) that means ansible check the desired state has already been achieved and exit without performing any actions if that state has been achieved. I you repeat the playbook execution or the tasks that use idempotent modules the final state is not changed.</p>
<p>Lets see an example of playbook:</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Update</span> <span class="hljs-string">web</span> <span class="hljs-string">servers</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">webservers</span>
  <span class="hljs-attr">become:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">vars:</span>
    <span class="hljs-attr">httpd_config_folder:</span> <span class="hljs-string">/etc/apache2</span>
    <span class="hljs-attr">vhost_domain:</span> <span class="hljs-string">example.local</span>
    <span class="hljs-attr">proxy_timeout:</span> <span class="hljs-number">60</span>
  <span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">apache</span> <span class="hljs-string">is</span> <span class="hljs-string">at</span> <span class="hljs-string">the</span> <span class="hljs-string">latest</span> <span class="hljs-string">version</span>
    <span class="hljs-attr">ansible.builtin.apt:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">apache2</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">latest</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Creates</span> <span class="hljs-string">the</span> <span class="hljs-string">html</span> <span class="hljs-string">directory</span> <span class="hljs-string">for</span> <span class="hljs-string">site</span>
    <span class="hljs-attr">ansible.builtin.file:</span>
      <span class="hljs-attr">dest:</span> <span class="hljs-string">'/var/www/vhosts/<span class="hljs-template-variable">{{ vhost_domain }}</span>/public_html'</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">directory</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">the</span> <span class="hljs-string">site</span> <span class="hljs-string">index.html</span>
    <span class="hljs-attr">ansible.builtin.template:</span>
      <span class="hljs-attr">src:</span> <span class="hljs-string">./templates/index.html.j2</span>
      <span class="hljs-attr">dest:</span> <span class="hljs-string">'/var/www/vhosts/<span class="hljs-template-variable">{{ vhost_domain }}</span>/public_html/index.html'</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Config</span> <span class="hljs-string">virtual</span> <span class="hljs-string">host</span> <span class="hljs-string">for</span> <span class="hljs-string">site</span>
    <span class="hljs-attr">ansible.builtin.template:</span>
      <span class="hljs-attr">src:</span> <span class="hljs-string">./templates/site-vhost.conf.j2</span>
      <span class="hljs-attr">dest:</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ httpd_config_folder }}</span>/sites-available/<span class="hljs-template-variable">{{ vhost_domain }}</span>.conf'</span>

    <span class="hljs-attr">notify:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Disables</span> <span class="hljs-string">apache</span> <span class="hljs-string">default</span> <span class="hljs-string">vhost</span> <span class="hljs-string">which</span> <span class="hljs-string">conflicts</span> <span class="hljs-string">with</span> <span class="hljs-string">others</span>
    <span class="hljs-attr">ansible.builtin.command:</span> <span class="hljs-string">"a2dissite 000-default.conf"</span>
    <span class="hljs-attr">args:</span>
      <span class="hljs-attr">removes:</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ httpd_config_folder }}</span>/sites-enabled/000-default.conf'</span>
    <span class="hljs-attr">notify:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Enable</span> <span class="hljs-string">VHost</span> <span class="hljs-string">for</span> <span class="hljs-string">Domain</span> {{ <span class="hljs-string">vhost_domain</span> }}  <span class="hljs-string">in</span> <span class="hljs-string">apache</span>
    <span class="hljs-attr">ansible.builtin.command:</span> <span class="hljs-string">"a2ensite <span class="hljs-template-variable">{{ vhost_domain }}</span>.conf"</span>
    <span class="hljs-attr">args:</span>
      <span class="hljs-attr">creates:</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ httpd_config_folder }}</span>/sites-enabled/<span class="hljs-template-variable">{{ vhost_domain }}</span>.conf'</span>
    <span class="hljs-attr">notify:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">Apache2</span> <span class="hljs-string">is</span> <span class="hljs-string">Started</span>
    <span class="hljs-attr">ansible.builtin.service:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">apache2</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">started</span>
  <span class="hljs-attr">handlers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
      <span class="hljs-attr">ansible.builtin.service:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">apache2</span>
        <span class="hljs-attr">state:</span> <span class="hljs-string">restarted</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Update</span> <span class="hljs-string">db</span> <span class="hljs-string">servers</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">databases</span>
  <span class="hljs-attr">become:</span> <span class="hljs-literal">true</span>

  <span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">postgresql</span> <span class="hljs-string">is</span> <span class="hljs-string">at</span> <span class="hljs-string">the</span> <span class="hljs-string">latest</span> <span class="hljs-string">version</span>
    <span class="hljs-attr">ansible.builtin.apt:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">latest</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">that</span> <span class="hljs-string">postgresql</span> <span class="hljs-string">is</span> <span class="hljs-string">started</span>
    <span class="hljs-attr">ansible.builtin.service:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">started</span>
</code></pre>
<p>This playbook do a lot of things:</p>
<ol>
<li>On webservers group install apache2 and make sure is updated</li>
<li>Creates folders for a vhost site public html</li>
<li>Creates a index.html for the vhost</li>
<li>Configures the vhost using a template that uses some variables like <strong>proxy_timeout</strong> and the <strong>vhost_domain</strong></li>
<li>Disables the apache default vhost which conflicts with all others, if the files exists. When changed notify a handler (trigger), that will restart apache</li>
<li>Enables the vhost site. When the symlink file is created in the enabled sites it will not run again. When changed notify a handler that will restart apache</li>
<li>Ensure apache is started</li>
<li>Declares a handler for apache restarts. Handlers are executed at the final of playbooks, only if the notification of some task is trigged</li>
<li>Install postgresl on databases group and ensure it's updated</li>
<li>Ensure postgresql server is started</li>
</ol>
<p>The inventory for this groups of machines could be something like:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">virtualmachines:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm01:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.4</span>
    <span class="hljs-attr">vm02:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.5</span>
    <span class="hljs-attr">vm03:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.6</span>
<span class="hljs-attr">webservers:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm01:</span>
    <span class="hljs-attr">vm02:</span>
<span class="hljs-attr">databases:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm03:</span>
</code></pre>
<p>And the templates are as follow:</p>
<p>index.html.j2 template</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"ie=edge"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Ansible Getting Started<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to {{ vhost_domain }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>  
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>site-vhost.conf.j2 template:</p>
<pre><code class="lang-plain">&lt;VirtualHost *:80&gt;
    ServerName {{ vhost_domain }}
    Timeout {{ proxy_timeout }}
    KeepAliveTimeout {{ proxy_timeout }}
    MaxKeepAliveRequests 0

    ServerAlias www.{{ vhost_domain }}

    DocumentRoot /var/www/vhosts/{{ vhost_domain }}/public_html

    &lt;Directory /var/www/vhosts/{{ vhost_domain }}/public_html&gt;
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
    &lt;/Directory&gt;



    CustomLog /var/log/apache2/{{ vhost_domain }}-access.log combined

    ErrorLog /var/log/apache2/{{ vhost_domain }}-error.log
    # Possible values include: debug, info, notice, warn, error, crit,

    # alert, emerg.

    LogLevel warn

&lt;/VirtualHost&gt;
</code></pre>
<p>The .j2 extension are for <a target="_blank" href="https://jinja.palletsprojects.com/en/3.1.x/">Jinja</a> templates which are the sintax for them.</p>
<p>If you run this playbook on the machines, you can check the html by adding a DNS local hostname line in your hosts file (/etc/hosts) like the one below</p>
<pre><code class="lang-plain">192.168.10.5 example.local
</code></pre>
<p>Any IP of webservers group should work.</p>
<p>You could check the ip with curl:</p>
<pre><code class="lang-plain">curl example.local
</code></pre>
<h2 id="heading-ansible-modules-and-collections">Ansible Modules and Collections</h2>
<p>The module is a reusable binary that ansible copies and executes on each node (when needed) to accomplish the action defined in each task. The modules are specific, for example: administering users on specific database, managing VLAN interfaces on a network device. Each task invokes a single module, and with playbooks you invoke several different modules..</p>
<p>Ansible &gt;= 2.0 recommends that you use the full qualified name of the module (for example <strong>ansible.builtin.copy</strong>), because of conflicts with single names.</p>
<p>The latest version of Ansible documentation talks about collections, which are a standard way to distribute playbooks, plugins, roles, and modules.</p>
<p>You can install collections with <code>ansible-galaxy</code> command or with a requirements file which you can distribute with your playbook codes.</p>
<p>The default installation of ansible comes with some collections pre-installed. You can check then with the command <code>ansible-galaxy collection list</code></p>
<p>There is a lot you can do with the ansible collections, like referencing then in playbooks, importing playbooks distributed in collections and many more. Check the documentation: <a target="_blank" href="https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook">https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook</a></p>
<p>We already saw a basic usage of the builtin collection in your previous playbook example.</p>
<p>To a list of all modules check this page: <a target="_blank" href="https://docs.ansible.com/ansible/latest/collections/index_module.html">https://docs.ansible.com/ansible/latest/collections/index_module.html</a></p>
<p>Some interesting collections are:</p>
<ul>
<li>amazon.aws - Manages AWS resources</li>
<li>community.aws - Manages AWS resources</li>
<li>ansible.builtin - The builtin collection with many usefull tools</li>
<li>ansible.posix - Tool to manage ACL, add SSH authorized keys, synchronize file with rsync, and more</li>
<li>azure.azcollection - Manages Azure resources</li>
<li><a target="_blank" href="http://community.azure">community.azure</a> - Manages Azure resources</li>
<li>community.digitalocean - Manages digital ocean resources</li>
<li>community.dns - Manages DNS zones on some providers.</li>
<li>community.docker - Manages docker containers</li>
<li>community.general - Many community modules for tools like keycloak, influxdb, FreeIPA and others, Java</li>
<li>community.google - Manages Google cloud resources</li>
<li>google.cloud - Manages Google cloud resources (more complete)</li>
<li>community.mysql - Manages Mysql databases</li>
<li>community.postgresql - Manages Postgresql databases</li>
<li>community.rabbitmq - Manages RabbitMQ exchanges, bindings, queues and other parameters.</li>
<li>community.vmware - Manages VMware virtualized infrastructure.</li>
<li>kubernetes.core - Core functionality for Kubernetes clusters</li>
</ul>
<p>And many more collections. You can also search in the galaxy repository: <a target="_blank" href="https://galaxy.ansible.com/">https://galaxy.ansible.com/</a></p>
<h2 id="heading-handlers">Handlers</h2>
<p>In the previous playbook we saw a handler in action. It execute a restart in apache only when need, or in other words, only when one of the tasks changes and trigger the actions. We need to declare the handler and to notify then. Here is another example:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Template</span> <span class="hljs-string">configuration</span> <span class="hljs-string">file</span>
  <span class="hljs-attr">ansible.builtin.template:</span>
    <span class="hljs-attr">src:</span> <span class="hljs-string">template.j2</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">/etc/foo.conf</span>
  <span class="hljs-attr">notify:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">memcached</span>

<span class="hljs-attr">handlers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">memcached</span>
    <span class="hljs-attr">ansible.builtin.service:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">memcached</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">restarted</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
    <span class="hljs-attr">ansible.builtin.service:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">apache</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">restarted</span>
</code></pre>
<p>This example notify two handlers: <strong>Restart memcached</strong> and <strong>Restart apache</strong></p>
<p>You can also name the handler different and listen to the same event (notify). In the follow example all two handlers will be executed:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">everything</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"this task will restart the web services"</span>
    <span class="hljs-attr">notify:</span> <span class="hljs-string">"restart web services"</span>

<span class="hljs-attr">handlers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">memcached</span>
    <span class="hljs-attr">service:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">memcached</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">restarted</span>
    <span class="hljs-attr">listen:</span> <span class="hljs-string">"restart web services"</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
    <span class="hljs-attr">service:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">apache</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">restarted</span>
    <span class="hljs-attr">listen:</span> <span class="hljs-string">"restart web services"</span>
</code></pre>
<p><strong>Controlling when handlers run</strong></p>
<p>By default handlers run after all tasks in a particular play have been completed. With this approach handlers runs only once, regardless of how many tasks notify it.</p>
<p>If you need to run handlers before the end of the play, add a task to flush the handlers after the one you want using the meta module. Example:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Some</span> <span class="hljs-string">tasks</span> <span class="hljs-string">go</span> <span class="hljs-string">here</span>
    <span class="hljs-attr">ansible.builtin.shell:</span> <span class="hljs-string">...</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Flush</span> <span class="hljs-string">handlers</span>
    <span class="hljs-attr">meta:</span> <span class="hljs-string">flush_handlers</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Some</span> <span class="hljs-string">other</span> <span class="hljs-string">tasks</span>
    <span class="hljs-attr">ansible.builtin.shell:</span> <span class="hljs-string">...</span>
</code></pre>
<p>For more information about handlers check the documentation: <a target="_blank" href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html">https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html</a></p>
<h2 id="heading-roles-and-the-recommend-layout-for-playbooks">Roles and the recommend layout for playbooks</h2>
<p>You can separate your playbooks into <strong>roles</strong>, and then apply roles to target nodes. This way you separate in another file the roles and can reuse them. Roles also store defaults, handlers, variables and tasks in separate directories, instead of a single long document. But first lest talk about the recommended layout to organize ansible playbooks and it will become more clear how roles works.</p>
<p>The directory layout recommended in the Ansible documentation is:</p>
<pre><code class="lang-plain">production                # inventory file for production servers
staging                   # inventory file for staging environment

group_vars/
   group1.yml             # here we assign variables to particular groups
   group2.yml
host_vars/
   hostname1.yml          # here we assign variables to particular systems
   hostname2.yml

library/                  # if any custom modules, put them here (optional)
module_utils/             # if any custom module_utils to support modules, put them here (optional)
filter_plugins/           # if any custom filter plugins, put them here (optional)

site.yml                  # master playbook
webservers.yml            # playbook for webserver tier
dbservers.yml             # playbook for dbserver tier

roles/
    common/               # this hierarchy represents a "role"
        tasks/            #
            main.yml      #  &lt;-- tasks file can include smaller files if warranted
        handlers/         #
            main.yml      #  &lt;-- handlers file
        templates/        #  &lt;-- files for use with the template resource
            ntp.conf.j2   #  &lt;------- templates end in .j2
        files/            #
            bar.txt       #  &lt;-- files for use with the copy resource
            foo.sh        #  &lt;-- script files for use with the script resource
        vars/             #
            main.yml      #  &lt;-- variables associated with this role
        defaults/         #
            main.yml      #  &lt;-- default lower priority variables for this role
        meta/             #
            main.yml      #  &lt;-- role dependencies
        library/          # roles can also include custom modules
        module_utils/     # roles can also include custom module_utils
        lookup_plugins/   # or other types of plugins, like lookup in this case

    webtier/              # same kind of structure as "common" was above, done for the webtier role
    monitoring/           # ""
    fooapp/               # ""
</code></pre>
<p>Alternatively you can put each inventory file with <strong>group_vars</strong> and <strong>host_vars</strong> in a separated directory. This is useful if your <strong>group_vars</strong> and <strong>host_vars</strong> don't have hat much in common in different environments. The layout could look like this:</p>
<pre><code class="lang-plain">inventories/
   production/
      hosts               # inventory file for production servers
      group_vars/
         group1.yml       # here we assign variables to particular groups
         group2.yml
      host_vars/
         hostname1.yml    # here we assign variables to particular systems
         hostname2.yml

   staging/
      hosts               # inventory file for staging environment
      group_vars/
         group1.yml       # here we assign variables to particular groups
         group2.yml
      host_vars/
         stagehost1.yml   # here we assign variables to particular systems
         stagehost2.yml

library/
module_utils/
filter_plugins/

site.yml
webservers.yml
dbservers.yml

roles/
    common/
    webtier/
    monitoring/
    fooapp/
</code></pre>
<p>In the roles directory you separate tasks, default variables, templates, and handlers and them apply this using targets in your playbook. Lets change your example of installing apache to follow this best practice.</p>
<p>The folder structure will be:</p>
<pre><code class="lang-plain">inventories
  local-hyperv
    group_vars
      webservers.yaml  
    hosts.yaml
  production
    group_vars
      webservers.yaml
    hosts.yaml
roles
  postgresl
    tasks
      main.yaml
  webserver
    handlers
      main.yaml
    tasks
      main.yaml
    templates
      index.html.j2
      site-vhost.conf.j2
    vars
      main.yaml
databases.yaml
site.yaml
webservers.yaml
</code></pre>
<p>Inside your inventory folder we create two environments: local-hyperv and production. We will override some variables to customize the playbooks in the group_vars folder, that overrides variables for groups of servers (there is also a host_vars folder that could be used to customize one host). In this example we create a webservers.yaml which overrides the variables for the webservers group.</p>
<p>local-hyperv webservers.yaml:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">vhost_domain:</span> <span class="hljs-string">example.local</span>
<span class="hljs-attr">proxy_timeout:</span> <span class="hljs-number">60</span>
</code></pre>
<p>production webservers.yaml:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">vhost_domain:</span> <span class="hljs-string">example-prod.local</span>
<span class="hljs-attr">proxy_timeout:</span> <span class="hljs-number">80</span>
</code></pre>
<p>Your hosts.yaml configuration differs only in the ip address of the servers.</p>
<p>local-hyperv hosts.yaml:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">virtualmachines:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm01:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.4</span>
    <span class="hljs-attr">vm02:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.5</span>
    <span class="hljs-attr">vm03:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.6</span>
<span class="hljs-attr">webservers:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm01:</span>
    <span class="hljs-attr">vm02:</span>
<span class="hljs-attr">databases:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm03:</span>
</code></pre>
<p>production hosts.yaml:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">virtualmachines:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm01:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.7</span>
    <span class="hljs-attr">vm02:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.8</span>
    <span class="hljs-attr">vm03:</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.9</span>
<span class="hljs-attr">webservers:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm01:</span>
    <span class="hljs-attr">vm02:</span>
<span class="hljs-attr">databases:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm03:</span>
</code></pre>
<p>We then create your roles folder, the postgresql roles just setup some tasks:</p>
<p>roles/postgresql/tasks/main.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">postgresql</span> <span class="hljs-string">is</span> <span class="hljs-string">at</span> <span class="hljs-string">the</span> <span class="hljs-string">latest</span> <span class="hljs-string">version</span>
  <span class="hljs-attr">ansible.builtin.apt:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">latest</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">that</span> <span class="hljs-string">postgresql</span> <span class="hljs-string">is</span> <span class="hljs-string">started</span>
  <span class="hljs-attr">ansible.builtin.service:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">started</span>
</code></pre>
<p>The webserver role is a little more complete it separated in handlers, tasks, templates and vars:</p>
<p>roles/webserver/handlers/main.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
  <span class="hljs-attr">ansible.builtin.service:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">apache2</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">restarted</span>
</code></pre>
<p>roles/webserver/templates/index.html.j2</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"ie=edge"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Ansible Getting Started<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to {{ vhost_domain }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>  
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>roles/webserver/templates/site-vhost.conf.j2</p>
<pre><code class="lang-plain">&lt;VirtualHost *:80&gt;
    ServerName {{ vhost_domain }}
    Timeout {{ proxy_timeout }}
    KeepAliveTimeout {{ proxy_timeout }}
    MaxKeepAliveRequests 0

    ServerAlias www.{{ vhost_domain }}

    DocumentRoot /var/www/vhosts/{{ vhost_domain }}/public_html

    &lt;Directory /var/www/vhosts/{{ vhost_domain }}/public_html&gt;
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
    &lt;/Directory&gt;



    CustomLog /var/log/apache2/{{ vhost_domain }}-access.log combined

    ErrorLog /var/log/apache2/{{ vhost_domain }}-error.log
    # Possible values include: debug, info, notice, warn, error, crit,

    # alert, emerg.

    LogLevel warn

&lt;/VirtualHost&gt;
</code></pre>
<p>roles/webserver/vars/main.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-attr">httpd_config_folder:</span> <span class="hljs-string">/etc/apache2</span>
<span class="hljs-attr">vhost_domain:</span> <span class="hljs-string">example.local</span>
<span class="hljs-attr">proxy_timeout:</span> <span class="hljs-number">60</span>
</code></pre>
<p>roles/webserver/tasks/main.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">apache</span> <span class="hljs-string">is</span> <span class="hljs-string">at</span> <span class="hljs-string">the</span> <span class="hljs-string">latest</span> <span class="hljs-string">version</span>
  <span class="hljs-attr">ansible.builtin.apt:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">apache2</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">latest</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Creates</span> <span class="hljs-string">the</span> <span class="hljs-string">html</span> <span class="hljs-string">directory</span> <span class="hljs-string">for</span> <span class="hljs-string">site</span>
  <span class="hljs-attr">ansible.builtin.file:</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">'/var/www/vhosts/<span class="hljs-template-variable">{{ vhost_domain }}</span>/public_html'</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">directory</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">the</span> <span class="hljs-string">site</span> <span class="hljs-string">index.html</span>
  <span class="hljs-attr">ansible.builtin.template:</span>
    <span class="hljs-attr">src:</span> <span class="hljs-string">./templates/index.html.j2</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">'/var/www/vhosts/<span class="hljs-template-variable">{{ vhost_domain }}</span>/public_html/index.html'</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Config</span> <span class="hljs-string">virtual</span> <span class="hljs-string">host</span> <span class="hljs-string">for</span> <span class="hljs-string">site</span>
  <span class="hljs-attr">ansible.builtin.template:</span>
    <span class="hljs-attr">src:</span> <span class="hljs-string">./templates/site-vhost.conf.j2</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ httpd_config_folder }}</span>/sites-available/<span class="hljs-template-variable">{{ vhost_domain }}</span>.conf'</span>
  <span class="hljs-attr">notify:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Disables</span> <span class="hljs-string">apache</span> <span class="hljs-string">default</span> <span class="hljs-string">vhost</span> <span class="hljs-string">which</span> <span class="hljs-string">conflicts</span> <span class="hljs-string">with</span> <span class="hljs-string">others</span>
  <span class="hljs-attr">ansible.builtin.command:</span> <span class="hljs-string">"a2dissite 000-default.conf"</span>
  <span class="hljs-attr">args:</span>
    <span class="hljs-attr">removes:</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ httpd_config_folder }}</span>/sites-enabled/000-default.conf'</span>
  <span class="hljs-attr">notify:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Enable</span> <span class="hljs-string">VHost</span> <span class="hljs-string">for</span> <span class="hljs-string">Domain</span> {{ <span class="hljs-string">vhost_domain</span> }}  <span class="hljs-string">in</span> <span class="hljs-string">apache</span>
  <span class="hljs-attr">ansible.builtin.command:</span> <span class="hljs-string">"a2ensite <span class="hljs-template-variable">{{ vhost_domain }}</span>.conf"</span>
  <span class="hljs-attr">args:</span>
    <span class="hljs-attr">creates:</span> <span class="hljs-string">'<span class="hljs-template-variable">{{ httpd_config_folder }}</span>/sites-enabled/<span class="hljs-template-variable">{{ vhost_domain }}</span>.conf'</span>
  <span class="hljs-attr">notify:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">Restart</span> <span class="hljs-string">apache</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">Apache2</span> <span class="hljs-string">is</span> <span class="hljs-string">Started</span>
  <span class="hljs-attr">ansible.builtin.service:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">apache2</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">started</span>
</code></pre>
<p>As you can see, we just refactor your previous site.yaml into separated chuncks. Ansible understands this role layout and put things in order for us.</p>
<p>To finalize lets check your databases.yaml, webservers.yaml, and site.yaml</p>
<p>databases.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">databases</span>
  <span class="hljs-attr">become:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">roles:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">postgresql</span>
</code></pre>
<p>webservers.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">webservers</span>
  <span class="hljs-attr">become:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">roles:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">webserver</span>
</code></pre>
<p>site.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">import_playbook:</span> <span class="hljs-string">webservers.yaml</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">import_playbook:</span> <span class="hljs-string">databases.yaml</span>
</code></pre>
<p>The <strong>databases.yaml</strong> file targets the hosts <em>databases</em> and apply the role <em>database</em>, the <strong>webservers.yaml</strong> file do the same for <em>webservers</em> group. Finally <strong>site.yaml</strong> file glues the entire playbook together importing other playbooks.</p>
<p>We can then run this playbook on the <strong>local-hyperv</strong> environment with the command:</p>
<pre><code class="lang-plain">ansible-playbook -i inventories/local-hyperv site.yaml
</code></pre>
<p>If we need to change environments, we just change the inventories folder.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>In this tutorial we cover the basics of Ansible so you can get started automating and documenting your infrastructure in sharable, replicable playbooks. One of core advantages of Ansible is it's lower starter requirements and easy to understand semantics.</p>
<p>Ansible Tower and Ansible Automation Platform are two products by Red</p>
<p>Hat that covers some limitations in Ansible core, by example its distributed authentication. Is the responsible of user to have root access to all machines through SSH, this could be a pain to maintain and rotate credentials in a big team. Another limitation of ansible core is if you want control who has access to different machines and environments, for the same reasons as the later, and also track which playbook has been applied and who applied it.</p>
<p>But Ansible core will serve you well if you do not have this specific requirements.</p>
<p>There is of course, some alternatives and complements to Ansible. I can mentioning a few with a brief summary of what it does:</p>
<ul>
<li>SaltStack - Main competitor of ansible, it has a different architecture using agent approach and a reactive event-driven infrastructure. The event drive means you can react to events on systems and proactively apply configurations. It also more performant than Ansible, it can run commands in thousands of systems in seconds. The agent approach means you need a master server, the advantage is that out of the box, Salt centralize the authentication of systems in the master node, so you don't have to mess up with distributed keys. Salt can also be used without the master-client using salt-ssh or salt-proxy. It has a lot of vocabulary to its components like: salt mine, salt minion, pillar, grains, wheel, etc. Which can be a little confuse at start. It's worth mention that it also has powerful tools in its ecosystem like the SaltStack Config that enables role based access control, multi master support, reporting, and others. More info in <a target="_blank" href="https://saltproject.io/">https://saltproject.io/</a></li>
<li>Terraform - Terraform has a different objective than Ansible. Terraform is focused on provision of infrastructure, Ansible is focused on the post provision of it. That means terraform is good to automate cloud infra resource allocation and configuration. Terraform uses a custom language to define its resources. It is complementary to Ansible. More info <a target="_blank" href="https://www.terraform.io/">https://www.terraform.io/</a></li>
<li>Pulumi - A competitor for terraform, its main focus is also in provision of infrastructure, the main difference is that uses a couple of mainstream programming languages such as Javascript, Typescript, Python, Go, C#, Java and Yaml. It's complementary to Ansible. More info in <a target="_blank" href="https://www.pulumi.com/">https://www.pulumi.com/</a></li>
</ul>
<p>All examples for this tutorial can be downloaded from <a target="_blank" href="https://github.com/giovannicandido/ansible-blog-post">https://github.com/giovannicandido/ansible-blog-post</a></p>
<h1 id="heading-references">References</h1>
<p><a target="_blank" href="https://docs.ansible.com/ansible-core/2.13/index.html">https://docs.ansible.com/ansible-core/2.13/index.html</a></p>
<p><a target="_blank" href="https://docs.ansible.com/ansible-core/2.13/getting_started/get_started_inventory.html">https://docs.ansible.com/ansible-core/2.13/getting_started/get_started_inventory.html</a></p>
<p><a target="_blank" href="https://docs.ansible.com/ansible-core/2.13/user_guide/intro_adhoc.html#intro-adhoc">https://docs.ansible.com/ansible-core/2.13/user_guide/intro_adhoc.html#intro-adhoc</a></p>
<p><a target="_blank" href="https://www.reddit.com/r/ansible/comments/92ds1w/accept_all_host_keys_one_time/">https://www.reddit.com/r/ansible/comments/92ds1w/accept_all_host_keys_one_time/</a></p>
<p><a target="_blank" href="https://docs.ansible.com/ansible/2.8/user_guide/playbooks_best_practices.html">https://docs.ansible.com/ansible/2.8/user_guide/playbooks_best_practices.html</a></p>
]]></content:encoded></item><item><title><![CDATA[Vagrant Hyper-v and Ansible for disposable infrastructure environments]]></title><description><![CDATA[In this article we will cover how to provision virtual machines and automate installation of servers using Vagrant, Hyper-v and Ansible. This can came in hand if you want to simulate a infrastructure provider and don't want to use cloud. Maybe you ha...]]></description><link>https://blog.gsilva.pro/vagrant-hyper-v-and-ansible-for-disposable-infrastructure-environments</link><guid isPermaLink="true">https://blog.gsilva.pro/vagrant-hyper-v-and-ansible-for-disposable-infrastructure-environments</guid><category><![CDATA[vagrant]]></category><category><![CDATA[ansible]]></category><category><![CDATA[hyper-v]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sat, 13 Aug 2022 23:46:17 GMT</pubDate><content:encoded><![CDATA[<p>In this article we will cover how to provision virtual machines and automate installation of servers using <a target="_blank" href="https://www.vagrantup.com/">Vagrant</a>, Hyper-v and <a target="_blank" href="https://www.ansible.com/">Ansible</a>. This can came in hand if you want to simulate a infrastructure provider and don't want to use cloud. Maybe you have enough RAM and processor power to run it locally.</p>
<p>Before we get started lets cover some basics.</p>
<h2 id="heading-alternatives-to-this-method">Alternatives to this method</h2>
<p>If you don't want use you local machine, and maybe want to mimic production workloads you can use <a target="_blank" href="https://www.terraform.io">Terraform</a> or <a target="_blank" href="https://www.pulumi.com/">Pulumi</a> as alternatives to vagrant. This two software are more capable of provisioning in cloud environments like AWS or Azure. This way you could as with minimal effort reuse it in production on your work. The downside is that you have to pay for resources you use and depending on usage could not be cost effective.</p>
<p>As of the ansible part it covers post provision, so you still have advantages by learning this tool, even in cloud environments.</p>
<p>If you just want a dev environment with local services like database, then the best and more productive alternative is to use docker and docker-compose to declare your services and run it in just one command. This performs better and as I said is more straightforward.</p>
<p>You can also replace hyper-v with other VM provider.</p>
<h2 id="heading-vagrant">Vagrant</h2>
<p>Vagrant is a virtual machine provision tool that can automate the creation of VM's with predefined configurations. In this tool you can replicate a environment without manual intervention (mostly)</p>
<p>Vagrant also supports different providers (virtual machine technologies) like Vmware, Virtualbox, Libvirt, and even some cloud providers.</p>
<p>It works by interpreting a declarative configuration file that describes your requirements, packages, operating system configuration, users, and more. It will download a VM disk from the internet and use it as a base for cloning and creating the VM.</p>
<p>Works on Windows, Mac and Linux</p>
<h2 id="heading-hyper-v">Hyper-V</h2>
<p>Hyper-v is a type 1 hypervisor build in in Windows Server, Windows &gt;= 8 and is a performant free way to run virtual machines, if you already have a windows license.</p>
<p>The type 1 means it runs directly on the hardware, and even the host OS also runs as a VM with elevated privileges. Because of this your Windows 10 (Host) machine will run as a VM, but don't worry, it has access to hardware acceleration and even running games on it should not have a big performance drop.</p>
<p>Its oriented on server workload so you don't have more customer oriented functionality like easy USB access in the guest VM, but you can run GUI software on Windows and Linux, although a little laggy because is a Remote Desktop session.</p>
<p><strong>Note</strong>: In the Microsoft documentation it says is available only in Pro, Enterprise and Educational editions of windows, but in fact you can enable it in Home edition. For example docker for windows in the WSL2 backend uses Hyper-V and you can nowadays do it in Home editions. For more info check <a target="_blank" href="https://www.makeuseof.com/install-hyper-v-windows-11-home/">https://www.makeuseof.com/install-hyper-v-windows-11-home/</a></p>
<p>I'm not covering all differences to alternatives like virtualbox and vmware play or workstation, but be in mind that it has a limited support for guest operation systems, like windows XP and some old Linux kernels, if you want a broad range of support you can use virtualbox or vmware which are type 2 hypervisors.</p>
<h2 id="heading-ansible">Ansible</h2>
<p>Ansible is an automation configuration tool that do not require an agent to run in the target machine. It connects to the machine thought SSH and runs commands and python configuration modules.</p>
<p>The only requirement is a previous installation of python in the machine, which most Linux distributions offer out of the box.</p>
<p>The majority of ansible modules and extensions are idempotent. That means it only performs operations when the state of the target machine differs from the desired state, which basically means you can run the ansible playbook many times that it will sync with the machines.</p>
<p>With ansible you can save time, eliminate repetitive tasks, have fewer mistakes and improve collaboration.</p>
<p>It works by reading configuration files and applying its modules states in target machines.</p>
<p>It has build in modules for apt get management, file management, script, managed system services and much more.</p>
<p><strong>Note</strong>: Is not in the scope of this article to teach you how to use this tools, previous basic knowledge is good</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>The first thing we need is the Hyper-V enabled. On Windows 10 or 11 Home follow <a target="_blank" href="https://www.makeuseof.com/install-hyper-v-windows-11-home/">this instructions</a></p>
<p>In Pro editions of windows follow <a target="_blank" href="https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v">this</a></p>
<p>Once you reboot you are good to install vagrant.</p>
<p><a target="_blank" href="https://www.vagrantup.com/downloads">Download and Install Vagrant</a></p>
<p>Once installed let's do some basic machine provision. Create a new directory, you can name it as you whish. I call it infra-hyperv</p>
<p>Open a powershell with admin rights (that's because hyperv requires admin privileges to interact)</p>
<p>In the folder you create run the command</p>
<pre><code class="lang-plain">vagrant init generic/ubuntu2204 # (1)
vagrant up --provider=hyperv # (2)
</code></pre>
<ol>
<li>We are create a Vagrantfile based on a VM image <strong>generic/ubuntu2204</strong>. Please note that this image has to have support for the provider</li>
<li>The second command brings the machine to life. It will download the image and run a 'default' machine</li>
</ol>
<p>You can check the machine running in Hyper-V manager:</p>
<p><img src="https://t454595.p.clickup-attachments.com/t454595/35bf7b97-4b52-45f2-91bd-d57f43214637/image.png" alt /></p>
<p>Vagrant adds the folder name to the machine name to avoid collisions.</p>
<p>You can ssh into the machine with the command:</p>
<pre><code class="lang-plain">vagrant ssh default
</code></pre>
<p>The output is something like:</p>
<p><img src="https://t454595.p.clickup-attachments.com/t454595/8ba15eb0-b8a0-4605-89f8-76edf2fc0c59/image.png" alt /></p>
<p>When vagrant creates the machine it will generate a ssh-key to authenticate with the guest.</p>
<p>The private key is created in <strong>.vagrant/machines/default/private_key</strong></p>
<p>Also the disks, snapshots and other information is in the .<strong>vagrant</strong> folder. So you must ignore this folder in version control systems.</p>
<p>Good, now there is a problem: Hyper-v will add a different ip to your machine each time it boots. That's a problem because we don't have a way to tell ansible this ip address from vagrant, we need a way to overcome this.</p>
<h3 id="heading-automating-static-ip-address-to-the-vms">Automating static ip address to the VM's</h3>
<p>This is not so simple as it may sound. In other providers vagrant can do it simpler using the Vagrantfile, but in hyper-v there is more to address.</p>
<p><strong>Theory</strong>:</p>
<ol>
<li>You need a internal only new Nat switch for hyper-v. Guests in this network will have access to any network resources to which the host has access, like Internet or a LAN.</li>
<li>Configure vagrant trigger that leverages the vagrant-reload plugin to change a given VM's network switch on-demand.</li>
<li>On initial provision (vagrant up) choose the <strong>Default Switch</strong>. So vagrant can provision the VM correctly</li>
<li>At the beginning of the provisioning process, configure a static IP within the VM's operating system.</li>
<li>Call <code>config.vm.provision :reload</code> which will file a trigger defined in step 2, thereby changing the VM's network switch to the new NatSwitch and issue <code>vagrant reload</code> and continue the provisioning process after the VM reboots.</li>
<li>When the VM reboots, it will acquire the static IP address from the NATSwitch and use it indefinitely</li>
</ol>
<p>Fortunately we can automate most of this process.</p>
<p><strong>1. Create the Nat Switch</strong></p>
<p>This process can be done once, manually but lest do it in a script called in the Vagrantfile.</p>
<p>Create the file <strong>scritps/create-nat-hyperv-switch.ps1</strong></p>
<pre><code class="lang-plain"># See: https://www.petri.com/using-nat-virtual-switch-hyper-v

If ("NATSwitch" -in (Get-VMSwitch | Select-Object -ExpandProperty Name) -eq $FALSE) {
    'Creating Internal-only switch named "NATSwitch" on Windows Hyper-V host...'

    New-VMSwitch -SwitchName "NATSwitch" -SwitchType Internal

    New-NetIPAddress -IPAddress 192.168.10.1 -PrefixLength 24 -InterfaceAlias "vEthernet (NATSwitch)"

    New-NetNAT -Name "NATNetwork" -InternalIPInterfaceAddressPrefix 192.168.10.0/24
}
else {
    '"NATSwitch" for static IP configuration already exists; skipping'
}

If ("192.168.10.1" -in (Get-NetIPAddress | Select-Object -ExpandProperty IPAddress) -eq $FALSE) {
    'Registering new IP address 192.168.10.1 on Windows Hyper-V host...'

    New-NetIPAddress -IPAddress 192.168.10.1 -PrefixLength 24 -InterfaceAlias "vEthernet (NATSwitch)"
}
else {
    '"192.168.10.1" for static IP configuration already registered; skipping'
}

If ("192.168.10.0/24" -in (Get-NetNAT | Select-Object -ExpandProperty InternalIPInterfaceAddressPrefix) -eq $FALSE) {
    'Registering new NAT adapter for 192.168.10.0/24 on Windows Hyper-V host...'

    New-NetNAT -Name "NATNetwork" -InternalIPInterfaceAddressPrefix 192.168.10.0/24
}
else {
    '"192.168.10.0/24" for static IP configuration already registered; skipping'
}
</code></pre>
<p>Then add the trigger on top of the Vagrantfile config section.</p>
<pre><code class="lang-plain">config.trigger.before :up do |trigger|
    trigger.info = "Creating 'NATSwitch' Hyper-V switch if it does not exist..."

    trigger.run = {privileged: "true", powershell_elevated_interactive: "true", path: "./scripts/create-nat-hyperv-switch.ps1"}
end
</code></pre>
<p><strong>2. Configure Vagrant Reload Trigger</strong></p>
<p>Install the reload plugin with the command:</p>
<pre><code class="lang-plain">vagrant plugin install vagrant-reload
</code></pre>
<p>Create a script to be executed: <strong>./scripts/set-hyperv-switch.ps1</strong></p>
<pre><code class="lang-plain"># See: https://www.thomasmaurer.ch/2016/01/change-hyper-v-vm-switch-of-virtual-machines-using-powershell/

Get-VM "default" | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "NATSwitch"
</code></pre>
<p>Configure the machine name in the hyper-v to be default instead of a generated one, so the script above can work. In Vagrantfile:</p>
<pre><code class="lang-plain">config.vm.define "default"
config.vm.provider :hyperv do |hy|
    hy.vmname = "default"
end
</code></pre>
<p>Configure the Vagrantfile to call the trigger, add just below the previous trigger created</p>
<pre><code class="lang-plain">config.trigger.before :reload do |trigger|
    trigger.info = "Setting Hyper-V switch to 'NATSwitch' to allow for static IP..."

    trigger.run = {privileged: "true", powershell_elevated_interactive: "true", path: "./scripts/set-hyperv-switch.ps1"}
end
</code></pre>
<p><strong>3. Configure Static IP Within Guest VM</strong></p>
<p>Configuring a static IP is an OS-specific task, so this procedure should be adjusted to suit the specific guest OS. We are using ubuntu:</p>
<p>Create a script in <strong>scripts/configure-static-ip.sh</strong></p>
<pre><code class="lang-plain">#!/bin/sh

echo 'Setting static IP address for Hyper-V...'

cat &lt;&lt; EOF &gt; /etc/netplan/01-netcfg.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: no
      addresses: [192.168.10.2/24]
      gateway4: 192.168.10.1
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
EOF
</code></pre>
<p>With the above script in place, add something like this just below the trigger definitions that we added earlier:</p>
<p><strong>4. Provision the VM</strong></p>
<pre><code class="lang-plain">vagrant destroy
vagrant up --provider=hyperv
</code></pre>
<p>Choose the Default Switch when prompted</p>
<h2 id="heading-using-multiple-vms">Using multiple VM's</h2>
<p>To use multiple VM's we need to adapt the scripts and Vagrant file. The results are as follow:</p>
<p><strong>Vagrantfile</strong></p>
<pre><code class="lang-plain">Vagrant.configure("2") do |config|

  config.trigger.before :up do |trigger|
    trigger.info = "Creating 'NATSwitch' Hyper-V switch if it does not exist..."

    trigger.run = {privileged: "true", powershell_elevated_interactive: "true", path: "./scripts/create-nat-hyperv-switch.ps1"}

  end

  config.trigger.before :reload do |trigger|
    trigger.info = "Setting Hyper-V switch to 'NATSwitch' to allow for static IP..."

    trigger.run = {privileged: "true", powershell_elevated_interactive: "true", path: "./scripts/set-hyperv-switch.ps1"}
  end

  config.vm.provision :reload
  config.vm.define :vm1 do |vm|
     vm.vm.box = "generic/ubuntu2204"
     vm.vm.provision "shell", path: "./scripts/configure-static-ip.sh", args: "192.168.10.2"
     vm.vm.provision :reload
     vm.vm.provider :hyperv do |hy|
      hy.vmname = "vm1"
      # hy.maxmemory = 1024
      hy.memory = 1024
      # hy.cpus = 2
    end
  end
  config.vm.define :vm2 do |vm|
    vm.vm.box = "generic/ubuntu2204"
    vm.vm.provision "shell", path: "./scripts/configure-static-ip.sh", args: "192.168.10.3"
    vm.vm.provision :reload
    vm.vm.provider :hyperv do |hy|
     hy.vmname = "vm2"
    #  hy.maxmemory = 1024
     hy.memory = 1024
    #  hy.cpus = 2
   end
 end


end
</code></pre>
<p><strong>scripts/configure-static-ip.sh</strong></p>
<pre><code class="lang-plain">#!/bin/sh

echo 'Setting static IP address for Hyper-V...'

cat &lt;&lt; EOF &gt; /etc/netplan/01-netcfg.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: no
      addresses: [$1/24]
      gateway4: 192.168.10.1
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
EOF

# Be sure NOT to execute "netplan apply" here, so the changes take effect on
# reboot instead of immediately, which would disconnect the provisioner.
</code></pre>
<p><strong>scripts/set-hyperv-switch.ps1</strong></p>
<pre><code class="lang-plain"># See: https://www.thomasmaurer.ch/2016/01/change-hyper-v-vm-switch-of-virtual-machines-using-powershell/

Get-VM "vm1" | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "NATSwitch"
Get-VM "vm2" | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "NATSwitch"
</code></pre>
<p>In the example above we create two machines vm1 and vm2 and set the ip address as 192.168.10.2 and 192.168.10.3 respectively</p>
<h2 id="heading-post-vm-provision-with-ansible">Post VM provision with Ansible</h2>
<p>Now is time to use ansible for the configuration of the VM. If you already know ansible this should be straightforward.</p>
<p>In this example we will configure apache and display a hello world page in both VM's</p>
<p>First we need to install ansible. It works best in a Linux environment, so I suggest you to install in WSL environment.</p>
<p>To get a WSL Ubuntu working check</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=n-J9438Mv-s">https://www.youtube.com/watch?v=n-J9438Mv-s</a></p>
<p>After that open a WSL shell and install ansible with the command:</p>
<pre><code class="lang-plain">sudo apt-get update
sudo apt-get install ansible
</code></pre>
<p>We will create a directory <strong>ansible</strong> and will add a inventory file name inventory/inventory.yaml</p>
<pre><code class="lang-yaml"><span class="hljs-attr">all:</span>
  <span class="hljs-attr">hosts:</span>
    <span class="hljs-attr">vm1:</span>
      <span class="hljs-attr">ansible_ssh_private_key_file:</span> <span class="hljs-string">../.vagrant/machines/vm1/hyperv/private_key</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.2</span>
    <span class="hljs-attr">vm2:</span>
      <span class="hljs-attr">ansible_ssh_private_key_file:</span> <span class="hljs-string">../.vagrant/machines/vm2/hyperv/private_key</span>
      <span class="hljs-attr">ansible_user:</span> <span class="hljs-string">vagrant</span>
      <span class="hljs-attr">ansible_host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.10</span><span class="hljs-number">.3</span>
</code></pre>
<p>Lets test ansible communication with the VM's</p>
<pre><code class="lang-plain">ansible -i inventory/inventory.yaml -m ping all
</code></pre>
<p>You will probably get a failure like this:</p>
<pre><code class="lang-plain">vm1 | UNREACHABLE! =&gt; {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.10.2 port 22: Connection timed out",
    "unreachable": true
}
vm2 | UNREACHABLE! =&gt; {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.10.3 port 22: Connection timed out",
    "unreachable": true
}
</code></pre>
<p>That is because WSL doesn't have access to the VSwitch the VM's are plugged. We can fix that with the following power shell:</p>
<pre><code class="lang-plain">Get-NetIPInterface | where {$_.InterfaceAlias -eq 'vEthernet (WSL)' -or $_.InterfaceAlias -eq 'vEthernet (NATSw
itch)'} | Set-NetIPInterface -Forwarding Enabled -Verbose
</code></pre>
<p>The command above set the Forward in the WSL Interface and in the NATSwitch interface to Enabled</p>
<p><strong>Fixing private key permissions</strong></p>
<p>We also need to fix the private key permissions because SSH required it. First we need to enable metadata attribute in the c: volume to permissions work</p>
<p>Temporally:</p>
<pre><code class="lang-plain">cd
sudo umount /mnt/c
sudo mount -t drvfs C: /mnt/c -o metadata
</code></pre>
<p>Permanently:</p>
<pre><code class="lang-plain">sudo vim /etc/wsl.conf
# add this lines
[automount]
enabled = true
root = /mnt
options = "metadata"
mountFsTab = true
</code></pre>
<p>Now change the group and permissions to be the same as your username, my is giova</p>
<pre><code class="lang-plain">sudo chown root:giova ../.vagrant/machines/vm1/hyperv/private_key
sudo chown root:giova ../.vagrant/machines/vm2/hyperv/private_key 
sudo chmod 660  ../.vagrant/machines/vm1/hyperv/private_key
sudo chmod 660 ../.vagrant/machines/vm2/hyperv/private_key
</code></pre>
<p>Now you can finally test the connection:</p>
<pre><code class="lang-plain">ansible -i inventory/inventory.yaml -m ping all
</code></pre>
<p>If is working you get the output:</p>
<pre><code class="lang-plain">vm2 | SUCCESS =&gt; {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
vm1 | SUCCESS =&gt; {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
</code></pre>
<p><strong>Creating the playbook</strong></p>
<p>Create the folder structure below:</p>
<pre><code class="lang-plain">inventory
|__inventory.yaml
roles
|__apache
   |__tasks
   |  |__main.yaml
   |__templates
   |__|__index.html.j2
|__site.yml
</code></pre>
<p>In the file <strong>roles/apache/tasks/main.yaml</strong> add the yaml configuration:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">install</span> <span class="hljs-string">apache</span> <span class="hljs-string">web</span> <span class="hljs-string">server</span>
  <span class="hljs-attr">apt:</span> <span class="hljs-string">name=apache2</span> <span class="hljs-string">state=present</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">add</span> <span class="hljs-string">hello</span> <span class="hljs-string">world</span> <span class="hljs-string">page</span> <span class="hljs-string">index.html</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">src:</span> <span class="hljs-string">../templates/index.html.j2</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">/var/www/html/index.html</span>
</code></pre>
<p>In <strong>roles/apache/template/index.html.j2</strong> add the html:</p>
<pre><code class="lang-plain">&lt;html&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Ansible Hello World&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Hello world from {{ inventory_hostname }} &lt;/h1&gt;
&lt;body&gt;
</code></pre>
<p>We created a role that</p>
<ol>
<li>Install apache2 package using apt-get in first task</li>
<li>Add a index.html page to /var/www/html/index.html with the contents of ../templates/index.html.j2 replacing all variables in the template (inventory_hostname is the name of the machine in the inventory of ansible)</li>
</ol>
<p>In <strong>site.yml</strong> add the code:</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">all</span>
  <span class="hljs-attr">become:</span> <span class="hljs-literal">yes</span>
  <span class="hljs-attr">roles:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">apache</span>
</code></pre>
<p>This code applies the apache role we create for all hosts. We need the become set to yes to be root because your ssh user is vagrant</p>
<p>Now run the playbook with the command:</p>
<pre><code class="lang-plain">ansible-playbook -i inventory/inventory.yaml site.yml
</code></pre>
<p>After the playbook you should check the pages in the VM's ip addresses</p>
<p>You can check all code for this article in <a target="_blank" href="https://github.com/giovannicandido/blog-hyperv-ansible-vagrant">https://github.com/giovannicandido/blog-hyperv-ansible-vagrant</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By using the tools presented in this article you can play with infrastructure automation and developer environment though virtual machines.</p>
<p>We cover up some limitations in Hyper-v and WSL in order to have the most streamline experience possible. Fortunately most of the procedures can be automated after you cover all the requirements.</p>
<h2 id="heading-references">References</h2>
<p><a target="_blank" href="https://automatingops.com/allowing-windows-subsystem-for-linux-to-communicate-with-hyper-v-vms">https://automatingops.com/allowing-windows-subsystem-for-linux-to-communicate-with-hyper-v-vms</a></p>
<p><a target="_blank" href="https://docs.ansible.com/ansible/2.3/playbooks_best_practices.html">https://docs.ansible.com/ansible/2.3/playbooks_best_practices.html</a></p>
<p><a target="_blank" href="https://superuser.com/questions/1354658/hyperv-static-ip-with-vagrant">https://superuser.com/questions/1354658/hyperv-static-ip-with-vagrant</a></p>
]]></content:encoded></item><item><title><![CDATA[Clickup Ferramenta de Gestão de Tarefas, Equipes Projetos, Documentos e Muito Mais]]></title><description><![CDATA[Nesse vídeo vamos fazer um overview da ótima ferramenta de gestão de projetos clickup que promete substituir várias ferramentas e centralizar o trabalho em equipe de forma produtiva e prática.
O clickup serve para equipes de todos os tamanhos, você n...]]></description><link>https://blog.gsilva.pro/clickup-ferramenta-de-gestao-de-tarefas-equipes-projetos-documentos-e-muito-mais</link><guid isPermaLink="true">https://blog.gsilva.pro/clickup-ferramenta-de-gestao-de-tarefas-equipes-projetos-documentos-e-muito-mais</guid><category><![CDATA[gestão]]></category><category><![CDATA[clickup]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Sun, 07 Aug 2022 22:20:38 GMT</pubDate><content:encoded><![CDATA[<p>Nesse vídeo vamos fazer um overview da ótima ferramenta de gestão de projetos clickup que promete substituir várias ferramentas e centralizar o trabalho em equipe de forma produtiva e prática.</p>
<p>O clickup serve para equipes de todos os tamanhos, você não precisa instalar nada e tem muitas funcionalidades no plano gratuito, além de poder colocar um numero ilimitado de pessoas no plano gratuito.</p>
<p>Funcionalidades como gestão de tarefas, boards kanban, documentos, automações, formulários, integrações com git, integrações diversas, chat, dashboards, whiteboards, e muito mais em um único lugar.</p>
<p>Site: <a target="_blank" href="https://www.clickup.com">https://www.clickup.com</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=JNbdzqmcC-0">https://www.youtube.com/watch?v=JNbdzqmcC-0</a></div>
<p>Mais referencias bacanas:</p>
<p>Curso Clickup Português: https://www.youtube.com/playlist?list...
Canal muito bom em inglês: https://www.youtube.com/c/LaylaPomper
Apresentação sobre processo scrum e exemplos de uso do clickup: https://youtu.be/AE4sPhaPbWU?t=3043</p>
]]></content:encoded></item><item><title><![CDATA[Humio Log parser with containerd and kubernetes]]></title><description><![CDATA[Using humio or other log aggregator like logstash with fluenbit as log shipper in kubernetes environment, I faced a problem with the log parser where the log messages where prepended by a date and stdout or stder information like in the line below:
2...]]></description><link>https://blog.gsilva.pro/humio-log-parser-with-containerd-and-kubernetes</link><guid isPermaLink="true">https://blog.gsilva.pro/humio-log-parser-with-containerd-and-kubernetes</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[k8s]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Tue, 16 Nov 2021 11:13:36 GMT</pubDate><content:encoded><![CDATA[<p>Using <a target="_blank" href="https://www.humio.com">humio</a> or other log aggregator like logstash with fluenbit as log shipper in kubernetes environment, I faced a problem with the log parser where the log messages where prepended by a date and stdout or stder information like in the line below:</p>
<pre><code><span class="hljs-attribute">2021</span>-<span class="hljs-number">11</span>-<span class="hljs-number">09</span>T<span class="hljs-number">16</span>:<span class="hljs-number">27</span>:<span class="hljs-number">39</span>.<span class="hljs-number">510951151</span>-<span class="hljs-number">05</span>:<span class="hljs-number">00</span> stdout F {<span class="hljs-string">"@timestamp"</span>:<span class="hljs-string">"2021-11-09T21:27:39.505Z"</span>,<span class="hljs-string">"@version"</span>:<span class="hljs-string">"1"</span>,<span class="hljs-string">"message"</span>:<span class="hljs-string">"HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@d89aadb (This connection has been closed.). Possibly consider using a shorter maxLifetime value."</span>,<span class="hljs-string">"logger_name"</span>:<span class="hljs-string">"com.zaxxer.hikari.pool.PoolBase"</span>,<span class="hljs-string">"thread_name"</span>:<span class="hljs-string">"http-nio-8080-exec-5"</span>,<span class="hljs-string">"level"</span>:<span class="hljs-string">"WARN"</span>,<span class="hljs-string">"level_value"</span>:<span class="hljs-number">30000</span>}
</code></pre><p>This cause parser errors when using json as a parser in humio.</p>
<p>The problem is when using containerd as the container runtime, (or cri-o) fluentbit needs a different parser to work with.</p>
<p>To solve this in humio use the following configuration:</p>
<pre><code>humio-fluentbit:
 parserConfig: |-
   [PARSER]
       Name apache
       Format regex
       Regex  ^(?&lt;host&gt;[^ ]*) [^ ]* (?&lt;user&gt;[^ ]*) \[(?&lt;<span class="hljs-keyword">time</span>&gt;[^\]]*)\] <span class="hljs-string">"(?&lt;method&gt;\S+)(?: +(?&lt;path&gt;[^\"]*?)(?: +\S*)?)?"</span> (?&lt;code&gt;[^ ]*) (?&lt;size&gt;[^ ]*)(?: <span class="hljs-string">"(?&lt;referer&gt;[^\"]*)"</span> <span class="hljs-string">"(?&lt;agent&gt;[^\"]*)"</span>)?$
       Time_Key <span class="hljs-keyword">time</span>
       Time_Format %d/%b/%Y:%H:%M:%S %z
   [PARSER]
       Name apache2
       Format regex
       Regex  ^(?&lt;host&gt;[^ ]*) [^ ]* (?&lt;user&gt;[^ ]*) \[(?&lt;<span class="hljs-keyword">time</span>&gt;[^\]]*)\] <span class="hljs-string">"(?&lt;method&gt;\S+)(?: +(?&lt;path&gt;[^ ]*) +\S*)?"</span> (?&lt;code&gt;[^ ]*) (?&lt;size&gt;[^ ]*)(?: <span class="hljs-string">"(?&lt;referer&gt;[^\"]*)"</span> <span class="hljs-string">"(?&lt;agent&gt;[^\"]*)"</span>)?$
       Time_Key <span class="hljs-keyword">time</span>
       Time_Format %d/%b/%Y:%H:%M:%S %z
   [PARSER]
       Name apache_error
       Format regex
       Regex  ^\[[^ ]* (?&lt;<span class="hljs-keyword">time</span>&gt;[^\]]*)\] \[(?&lt;level&gt;[^\]]*)\](?: \[pid (?&lt;pid&gt;[^\]]*)\])?( \[client (?&lt;client&gt;[^\]]*)\])? (?&lt;message&gt;.*)$
   [PARSER]
       Name nginx
       Format regex
       Regex ^(?&lt;remote&gt;[^ ]*) (?&lt;host&gt;[^ ]*) (?&lt;user&gt;[^ ]*) \[(?&lt;<span class="hljs-keyword">time</span>&gt;[^\]]*)\] <span class="hljs-string">"(?&lt;method&gt;\S+)(?: +(?&lt;path&gt;[^\"]*?)(?: +\S*)?)?"</span> (?&lt;code&gt;[^ ]*) (?&lt;size&gt;[^ ]*)(?: <span class="hljs-string">"(?&lt;referer&gt;[^\"]*)"</span> <span class="hljs-string">"(?&lt;agent&gt;[^\"]*)"</span>)
       Time_Key <span class="hljs-keyword">time</span>
       Time_Format %d/%b/%Y:%H:%M:%S %z
   [PARSER]
       Name json
       Format json
       Time_Key <span class="hljs-keyword">time</span>
       Time_Format %d/%b/%Y:%H:%M:%S %z
   [PARSER]
       Name docker
       Format json
       Time_Key <span class="hljs-keyword">time</span>
       Time_Format %Y-%m-%dT%H:%M:%S.%L
       Time_Keep   On
   [PARSER]
       Name syslog
       Format regex
       Regex ^\&lt;(?&lt;pri&gt;[<span class="hljs-number">0</span>-<span class="hljs-number">9</span>]+)\&gt;(?&lt;<span class="hljs-keyword">time</span>&gt;[^ ]* {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>}[^ ]* [^ ]*) (?&lt;host&gt;[^ ]*) (?&lt;ident&gt;[a-zA-Z<span class="hljs-number">0</span>-<span class="hljs-number">9_</span>\/\.\-]*)(?:\[(?&lt;pid&gt;[<span class="hljs-number">0</span>-<span class="hljs-number">9</span>]+)\])?(?:[^\:]*\:)? *(?&lt;message&gt;.*)$
       Time_Key <span class="hljs-keyword">time</span>
       Time_Format %b %d %H:%M:%S
   [PARSER]
       Name cri
       Format regex
       Regex ^(?&lt;<span class="hljs-keyword">time</span>&gt;[^ ]+) (?&lt;stream&gt;stdout|stderr) (?&lt;logtag&gt;[^ ]*) (?&lt;<span class="hljs-keyword">log</span>&gt;.*)$
       Time_Key <span class="hljs-keyword">time</span>
       Time_Format %Y-%m-%dT%H:%M:%S.%L%z
 inputConfig: |-
   [INPUT]
     Name             tail
     Path             /var/<span class="hljs-keyword">log</span>/containers/*.log
     Parser           cri
     Tag              kube.*
     Refresh_Interval <span class="hljs-number">5</span>
     Mem_Buf_Limit    <span class="hljs-number">5</span>MB
     Skip_Long_Lines  On
</code></pre><p>This configuration adds a new parser (cri) and overrides it in <strong>inputConfig</strong>.</p>
<p>You can adapt this solution for your fluentbit configuration.</p>
<p>For more information check this url: <a target="_blank" href="https://github.com/microsoft/fluentbit-containerd-cri-o-json-log">https://github.com/microsoft/fluentbit-containerd-cri-o-json-log</a></p>
]]></content:encoded></item><item><title><![CDATA[Lens uma IDE para Kubernetes]]></title><description><![CDATA[Confira o meu overview da ferramenta Lens que oferece uma visão produtiva de clusters kubernetes, podendo interagir com todos os objetos de sua api de forma fácil e rápida.
https://www.youtube.com/watch?v=e5VA9huSb3k]]></description><link>https://blog.gsilva.pro/lens-uma-ide-para-kubernetes</link><guid isPermaLink="true">https://blog.gsilva.pro/lens-uma-ide-para-kubernetes</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[k8s]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Tue, 16 Nov 2021 11:03:17 GMT</pubDate><content:encoded><![CDATA[<p>Confira o meu overview da ferramenta <a target="_blank" href="https://k8slens.dev/">Lens</a> que oferece uma visão produtiva de clusters kubernetes, podendo interagir com todos os objetos de sua api de forma fácil e rápida.</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=e5VA9huSb3k">https://www.youtube.com/watch?v=e5VA9huSb3k</a></p>
]]></content:encoded></item><item><title><![CDATA[Fixing bind address of kube-proxy metrics for prometheus monitoring]]></title><description><![CDATA[Using kubespray to install k8s the default bind address for kube-proxy metrics endpoint is localhost:10249. This makes prometheus community not scrap the metrics because it needs access at the host network level.
To solve this follow this simple step...]]></description><link>https://blog.gsilva.pro/fixing-bind-address-of-kube-proxy-metrics-for-prometheus-monitoring</link><guid isPermaLink="true">https://blog.gsilva.pro/fixing-bind-address-of-kube-proxy-metrics-for-prometheus-monitoring</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[monitoring]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Fri, 05 Nov 2021 14:14:48 GMT</pubDate><content:encoded><![CDATA[<p>Using kubespray to install k8s the default bind address for kube-proxy metrics endpoint is localhost:10249. This makes prometheus community not scrap the metrics because it needs access at the host network level.
To solve this follow this simple steps.</p>
<p>If using kubespray to setup the k8s add this line in your inventory k8s-cluster.yaml</p>
<pre><code><span class="hljs-selector-tag">kube_proxy_metrics_bind_address</span>: 0<span class="hljs-selector-class">.0</span><span class="hljs-selector-class">.0</span><span class="hljs-selector-class">.0</span><span class="hljs-selector-pseudo">:10249</span>
</code></pre><p>You can eighter rerun the playbook (which takes at last 30 minutes) or you can change manually:</p>
<p>SSH in the hosts
Edit the file: <code>/etc/kubernetes/kubeadm-config.yaml</code> change the line <code>metricsBindAddress: 0.0.0.0:10249</code></p>
<p>Repeat the process for all nodes running kube-proxy</p>
<p>Now run the command:</p>
<pre><code> kubectl --kubeconfig /etc/kubernetes/admin.conf -n kube-system <span class="hljs-keyword">get</span> configmap kube-proxy -o yaml | sed <span class="hljs-string">'s/metricsBindAddress: 127.0.0.1:10249/metricsBindAddress: 0.0.0.0:10249/g'</span> | kubectl --kubeconfig /etc/kubernetes/admin.conf apply -f -
</code></pre><p>And restart kube-proxy:</p>
<pre><code>kubectl --kubeconfig /etc/kubernetes/admin.conf <span class="hljs-keyword">delete</span> pod -n kube-<span class="hljs-keyword">system</span> -l k8s-app=kube-proxy --force --grace-period=<span class="hljs-number">0</span>
</code></pre><p>Test if works, and is done</p>
]]></content:encoded></item><item><title><![CDATA[Configuring prometheus to scrap SSL etcd cluster]]></title><description><![CDATA[In this setup we will configure the prometheus community helm chart to scrap metrics from a SSL enabled etcd.
The etcd was installed through kupespray kubernetes cluster, but you should be able to adapt to your case.
Generating the SSL Client Keys
We...]]></description><link>https://blog.gsilva.pro/configuring-prometheus-to-scrap-ssl-etcd-cluster</link><guid isPermaLink="true">https://blog.gsilva.pro/configuring-prometheus-to-scrap-ssl-etcd-cluster</guid><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Giovanni Silva]]></dc:creator><pubDate>Tue, 02 Nov 2021 18:24:12 GMT</pubDate><content:encoded><![CDATA[<p>In this setup we will configure the prometheus community helm chart to scrap metrics from a SSL enabled etcd.</p>
<p>The etcd was installed through kupespray kubernetes cluster, but you should be able to adapt to your case.</p>
<h1 id="generating-the-ssl-client-keys">Generating the SSL Client Keys</h1>
<p>We will need to generate client certificate keys to connect to etcd.</p>
<p>Kubespray install the CA pem in <strong>/etc/ssl/etcd/ssl/</strong> directory on each node</p>
<p>SSH into the first etcd node and generate a new ssl client certificate. The first node is the one with the file <strong>/etc/ssl/etcd/openssl.conf</strong>, you can find the file with the command <code>find / -name openssl.conf</code></p>
<p>Create the client request:</p>
<pre><code>openssl req -config /etc/ssl/etcd/openssl.conf -<span class="hljs-built_in">new</span> -nodes   -keyout etcd-client-prometheus.key -<span class="hljs-keyword">out</span> etcd-client-prometheus.csr -subj "/CN=etcd-client"
</code></pre><p>Signing the request</p>
<pre><code>openssl ca -config openssl.cnf -extensions etcd_client -keyfile <span class="hljs-keyword">private</span>/ca.key -cert certs/ca.crt -<span class="hljs-keyword">out</span> certs/etcd-client.crt -infiles etcd-client.csr
</code></pre><p>Generate the certs</p>
<pre><code>openssl x509 -req -<span class="hljs-keyword">in</span> etcd-client-prometheus.csr -<span class="hljs-built_in">CAkey</span> /etc/ssl/etcd/ssl/ca-key.pem -CA /etc/ssl/etcd/ssl/ca.pem -<span class="hljs-built_in">CAcreateserial</span> -days <span class="hljs-number">36500</span> -extensions ssl_client -<span class="hljs-keyword">out</span> etcd-client-prometheus.crt -extfile /etc/ssl/etcd/openssl.conf
</code></pre><p>Test cert</p>
<pre><code>curl <span class="hljs-comment">--cert etcd-client-prometheus.crt --key etcd-client-prometheus.key https://10.8.0.2:2379/metrics</span>
</code></pre><p>Change the ip for your server ip</p>
<h1 id="configuring-prometheus-to-user-the-certs">Configuring Prometheus to user the certs</h1>
<p>Create a secret to old the certs (must be in same namespace as prometheus pods)</p>
<pre><code>kubectl create secret generic etcd-client-cert --<span class="hljs-keyword">from</span>-file=./ca.pem --<span class="hljs-keyword">from</span>-file=./etcd-client-prometheus.crt --<span class="hljs-keyword">from</span>-file=./etcd-client-prometheus.key
</code></pre><p>Configure Prometheus community to read the secret and to use it in etcd monitoring:</p>
<pre><code class="lang-yaml"><span class="hljs-comment">## Component scraping etcd</span>
<span class="hljs-comment">##</span>
<span class="hljs-attr">kubeEtcd:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
  <span class="hljs-comment">## If your etcd is not deployed as a pod, specify IPs it can be found on</span>
  <span class="hljs-comment">##</span>
  <span class="hljs-attr">endpoints:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-number">10.8</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>
    <span class="hljs-bullet">-</span> <span class="hljs-number">10.8</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>
    <span class="hljs-bullet">-</span> <span class="hljs-number">10.8</span><span class="hljs-number">.0</span><span class="hljs-number">.3</span>

  <span class="hljs-comment">## Etcd service. If using kubeEtcd.endpoints only the port and targetPort are used</span>
  <span class="hljs-comment">##</span>
  <span class="hljs-attr">service:</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">2379</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">2379</span>
    <span class="hljs-comment"># selector:</span>
    <span class="hljs-comment">#   component: etcd</span>
  <span class="hljs-attr">serviceMonitor:</span>
    <span class="hljs-attr">scheme:</span> <span class="hljs-string">https</span>
    <span class="hljs-attr">insecureSkipVerify:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">serverName:</span> <span class="hljs-string">etcd.kube-system.svc</span> <span class="hljs-comment"># this is per kubespray default install</span>
    <span class="hljs-attr">caFile:</span> <span class="hljs-string">/etc/prometheus/secrets/etcd-client-cert/ca.pem</span>
    <span class="hljs-attr">certFile:</span> <span class="hljs-string">/etc/prometheus/secrets/etcd-client-cert/etcd-client-prometheus.crt</span>
    <span class="hljs-attr">keyFile:</span> <span class="hljs-string">/etc/prometheus/secrets/etcd-client-cert/etcd-client-prometheus.key</span>
<span class="hljs-attr">prometheus:</span>
  <span class="hljs-attr">prometheusSpec:</span>
    <span class="hljs-attr">secrets:</span> [<span class="hljs-string">"etcd-client-cert"</span>]
</code></pre>
<p>Apply the new values, for example with the command:</p>
<pre><code>helm upgrade -f <span class="hljs-keyword">values</span>.yml prometheus prometheus-community/kube-prometheus-stack
</code></pre><p>Check your targets in prometheus and your etcd dashboard in grafana.</p>
]]></content:encoded></item></channel></rss>