tag:blogger.com,1999:blog-25342439126372355912024-03-27T08:28:34.794+01:00Python/DevOpsI had been writing mostly about python, but recently I write especially about DevOps and how to automate things.tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.comBlogger81125tag:blogger.com,1999:blog-2534243912637235591.post-36029391551148039942021-05-19T05:30:00.001+02:002021-05-19T11:49:03.115+02:00Kafka and Kubernetes - How to "dump" topic?<p style="text-align: left;">Some time ago, I wanted to debug messages on a specific topic in <a href="https://kafka.apache.org" target="_blank">Kafka</a>, but I did not want to do that on production (we set up Kafka on Kubernetes using <a href="https://github.com/bitnami/charts/tree/master/bitnami/kafka/" target="_blank">bitnami chart</a>). So, the question was, how can I do that? How can I debug something on production? It almost always a bad idea, so I wanted to "dump" a topic on my local machine.</p><p style="text-align: left;">Something like "dump of topic" does not exist of course, because what does it mean in Kafka world? 🤔 But, you can create a consumer which will consume the topic from the beginning and pipe messages into a file; then using producer you can put these messages onto a specific topic on your local machine.</p><p style="text-align: left;">Remember that my Kafka Cluster is deployed on Kubernetes Cluster. I can "ssh" onto Kafka pod, run command and then copy the file on my local machine. Or, I can just exec a command on a Kafka pod and pipe the output (messages) to my local machine and save the messages locally.</p><p style="text-align: left;">Here is the comamnd:</p>
<script src="https://gist.github.com/tomislater/0aa579438c59a3cc9a2baf08f0218d2a.js"></script>
<p style="text-align: left;">You tell <i>kafka-0</i> pod that you want to run the specific command on this pod; and the output of this command should be piped to <i>/tmp/name-of-my-topic</i> file on your local machine.</p><p style="text-align: left;">You have messages in this file. Now, you should be able to "produce" them on your local Kafka via:</p>
<script src="https://gist.github.com/tomislater/df3abfef4407f5a15c8f05800a16ff99.js"></script>
<p style="text-align: left;">Here is a script which will dump all topics (excluding internal topics) to your local machine:</p>
<script src="https://gist.github.com/tomislater/5932fd7e0df42cc0c3d865c083dfe8bd.js"></script>
<p style="text-align: left;">I hope it was useful! 👋</p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-62368410806493311712021-02-09T14:31:00.006+01:002021-02-10T21:55:35.361+01:00Pipeline Editor in GitLab - test your CI configs<p style="text-align: left;">Pipeline Editor has been introduced in <a href="https://gitlab.com/groups/gitlab-org/-/epics/4540">13.8</a> version. What can you do with this tool? Normally, when you edit your <i>.gitlab-ci.yml</i> file, you will find that something is wrong with the code only after you push your changes. It would be great to know that there is a typo or something extremely wrong with this config in advance, right?</p><p style="text-align: left;">And for this, you can use <a href="https://docs.gitlab.com/ee/ci/pipeline_editor/" target="_blank">Pipeline Editor</a>. There are three things you can do with that tool:</p><p style="text-align: left;"></p><ul style="text-align: left;"><li>Validate pipeline configuration</li><li>Visualise the configuration</li><li>Lint the configuration</li></ul><p style="text-align: left;">Validation is done automatically when you're editing the file. Okay, so how to start? 🤔 Go to <b>CI / CD</b> section and click the <b>Editor</b> link:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjZFij0JvrV1rg_IRNhiFGrnSReq2tLHWIun_NkBjQxpRWJyxfj1NCitLiIKh_Po17FMHW6xwgzhBMVE4vd-x7QlsK36m6dI60rfrlHUX3Zjh63T8D-Uf0mh3kXrRemSotQo5Zqx3Plxc/s438/Screen+Shot+2021-02-08+at+12.23.52.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="332" data-original-width="438" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjZFij0JvrV1rg_IRNhiFGrnSReq2tLHWIun_NkBjQxpRWJyxfj1NCitLiIKh_Po17FMHW6xwgzhBMVE4vd-x7QlsK36m6dI60rfrlHUX3Zjh63T8D-Uf0mh3kXrRemSotQo5Zqx3Plxc/w200-h152/Screen+Shot+2021-02-08+at+12.23.52.png" width="200" /></a></div><h2 style="text-align: left;">Validation</h2><p style="text-align: left;">After that, you should see the Pipeline Editor:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDDGU7ThbugNmlfLVDzUn7MOOoWQrmAiCe2NVShGQJNOAs5-O_qLc-eZYVpFJhTMPA-ZnxCb_VBiwyVGLGlz42zL9GjzgCotQ8u0UrlPCgIl4M1Fd8fNcOdGC6MZWXRlYDikz2T1BTbzA/s1926/Screen+Shot+2021-02-08+at+12.25.52.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1326" data-original-width="1926" height="442" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDDGU7ThbugNmlfLVDzUn7MOOoWQrmAiCe2NVShGQJNOAs5-O_qLc-eZYVpFJhTMPA-ZnxCb_VBiwyVGLGlz42zL9GjzgCotQ8u0UrlPCgIl4M1Fd8fNcOdGC6MZWXRlYDikz2T1BTbzA/w640-h442/Screen+Shot+2021-02-08+at+12.25.52.png" width="640" /></a></div><p style="text-align: left;">Let's break something!</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXFb3fdU5N-1ZLiNsoRLq8MNBll9UY6Ra57x-A6Nb9FPgdYvpxNh4UcgarJy6igrdvYZf4eyv1KGG3-Xq4mY3BDe5vAtAN3rZle5ORitn1zgogzJmkYNBpvUjLCY_7DsZkKFMkcuSTDYQ/s1886/Screen+Shot+2021-02-08+at+12.33.48.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="778" data-original-width="1886" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXFb3fdU5N-1ZLiNsoRLq8MNBll9UY6Ra57x-A6Nb9FPgdYvpxNh4UcgarJy6igrdvYZf4eyv1KGG3-Xq4mY3BDe5vAtAN3rZle5ORitn1zgogzJmkYNBpvUjLCY_7DsZkKFMkcuSTDYQ/w640-h264/Screen+Shot+2021-02-08+at+12.33.48.png" width="640" /></a></div><p style="text-align: left;">Hmm, what's wrong with this code? 🤔 I've received a message that my CI configuration is invalid because chosen stage does not exist. This make sense! I have a <i>build</i> job and I inherit from hidden job <i>.default</i>, and I had not set a stage in any of these jobs. But, I should! Thank you Pipeline Editor.</p><p style="text-align: left;">Let's fix CI configuration and add a <i>build</i> stage to a <i>build</i> job:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCsXiUumO4y4qPoNlnem5qQ82NBoQDwLZn1q4M5CNq6W2YdsOW4HiE9_0PTigi2Oxba_bTH-PxTL7pMgrYHDyxLhVMICK0e-dwiURjVy742GMuT126H0LE3ybyQuFWocFJjZLZsasPwO8/s438/Screen+Shot+2021-02-08+at+16.34.23.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="190" data-original-width="438" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCsXiUumO4y4qPoNlnem5qQ82NBoQDwLZn1q4M5CNq6W2YdsOW4HiE9_0PTigi2Oxba_bTH-PxTL7pMgrYHDyxLhVMICK0e-dwiURjVy742GMuT126H0LE3ybyQuFWocFJjZLZsasPwO8/s320/Screen+Shot+2021-02-08+at+16.34.23.png" width="320" /></a></div><h2>Linter</h2><p style="text-align: left;">CI configuration is valid now, I can go to the <i>Lint</i> section and check if syntax is correct:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR5d5JKSLK84wHVxYwssFZzyDHFzG7nNL-mnTVFWz95_crlc5ha1LH2rL-1ZiHZEm-2uy1oDOxVGOOo7vjWy963ChYYcJhI5XSsTpLFQthTjhFm2Xc8zEnZVp3YWEb6luTIPzoWZEGsUw/s1840/Screen+Shot+2021-02-08+at+16.35.36.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1218" data-original-width="1840" height="424" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjR5d5JKSLK84wHVxYwssFZzyDHFzG7nNL-mnTVFWz95_crlc5ha1LH2rL-1ZiHZEm-2uy1oDOxVGOOo7vjWy963ChYYcJhI5XSsTpLFQthTjhFm2Xc8zEnZVp3YWEb6luTIPzoWZEGsUw/w640-h424/Screen+Shot+2021-02-08+at+16.35.36.png" width="640" /></a></div><p style="text-align: left;">Everything looks fine here! Syntax is correct and I can even see the preview of my jobs! Nice 👍. Linter checks for syntax and logical errors and it's more powerful than the validation in the editor. I see that the <i>build</i> job will run on branches and tags, and because it's the first job in a pipeline with <i>when</i> set to <i>on_success</i> it will be always spawned.</p><p style="text-align: left;">Let's add another job, but this time for deploy:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikOnLTZhJptwzQzecUXn-S8hlm6j9iAxo0ovxu8Q2jitWEwRx4vO37_CzXTros-ih6by_VB2B-UnApp2WRagkeWGkQ3M52xeH1TR-Ra5adRgzpTAOqLsWfM3x6fAxtDmwlIdITzl5rx3M/s390/Screen+Shot+2021-02-08+at+21.05.52.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="240" data-original-width="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikOnLTZhJptwzQzecUXn-S8hlm6j9iAxo0ovxu8Q2jitWEwRx4vO37_CzXTros-ih6by_VB2B-UnApp2WRagkeWGkQ3M52xeH1TR-Ra5adRgzpTAOqLsWfM3x6fAxtDmwlIdITzl5rx3M/s320/Screen+Shot+2021-02-08+at+21.05.52.png" width="320" /></a></div><p style="text-align: left;">Now, when I navigate to <i>Lint</i> section I will see two jobs:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzhdSwDEyqgnZwqX8pTPNlPdgSYULSjCapWDnjvGW3DOcukFeuACkeJvI_N6RO50Jj7Nxzgpi9L3dDRcExsI9rsINHhHGPjWL8tGLsP80_-L_RkWlygXlRBGgVOR-1hqucvMXKk6j-1Hk/s1794/Screen+Shot+2021-02-08+at+21.06.44.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="882" data-original-width="1794" height="314" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzhdSwDEyqgnZwqX8pTPNlPdgSYULSjCapWDnjvGW3DOcukFeuACkeJvI_N6RO50Jj7Nxzgpi9L3dDRcExsI9rsINHhHGPjWL8tGLsP80_-L_RkWlygXlRBGgVOR-1hqucvMXKk6j-1Hk/w640-h314/Screen+Shot+2021-02-08+at+21.06.44.png" width="640" /></a></div><p style="text-align: left;">Great 👍. In this way, you can lint your changes before committing them. Linter is updated in real-time. And you can see also some additional info like <i>deploy</i> job is manual.</p><h2 style="text-align: left;">Visualization </h2><p style="text-align: left;">Okay, and now the next section: Visualize. If you go there now you will see these two jobs:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhBJSB9yWNrsqQ_vs12f2nB3ijIh9ySQHCqhvhqngmcMRew7WxC2smRaMuGzF9nJmvz9ORqg5TiweDChFyeeYmlZNA8QA4pkatLYbvV1JN_a8AWjV7hkszIYKTjY0BlNYsjPIHZyECBzw/s1032/Screen+Shot+2021-02-08+at+21.33.13.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="468" data-original-width="1032" height="181" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhBJSB9yWNrsqQ_vs12f2nB3ijIh9ySQHCqhvhqngmcMRew7WxC2smRaMuGzF9nJmvz9ORqg5TiweDChFyeeYmlZNA8QA4pkatLYbvV1JN_a8AWjV7hkszIYKTjY0BlNYsjPIHZyECBzw/w400-h181/Screen+Shot+2021-02-08+at+21.33.13.png" width="400" /></a></div><p style="text-align: left;">You don't need this feature if you have two or three jobs. But, what if you have more jobs? It would be useful to visualize them. Let's see another example:</p>
<script src="https://gist.github.com/tomislater/b7901b4b6ee3ba3aef6b8cfa44e87572.js"></script>
<p style="text-align: left;">It's not a complicated one; but even in this example, a visualization will be very useful. And here is the viz:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd4WvZViZKWNTublbZgct3F3Pw-BXotwo7RjzWh4qHCWIKwVY0vPoNHDn9Pjlv90DB-d7dr2fQANx9U9emfpxo6PmltLt7-9s9MTvB8DBWCbxVzk3mqUN7A_ftpD2UNUNiET7fCfG_ozY/s1994/Screen+Shot+2021-02-08+at+21.57.21.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="604" data-original-width="1994" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd4WvZViZKWNTublbZgct3F3Pw-BXotwo7RjzWh4qHCWIKwVY0vPoNHDn9Pjlv90DB-d7dr2fQANx9U9emfpxo6PmltLt7-9s9MTvB8DBWCbxVzk3mqUN7A_ftpD2UNUNiET7fCfG_ozY/w640-h194/Screen+Shot+2021-02-08+at+21.57.21.png" width="640" /></a></div><p style="text-align: left;">Much better than plain yaml, right? Instantly, you know what's going on here. You have four stages, the first job is <i>lint</i>. Then, an image is built. And now, because I used <i>needs</i> keyword, I see relationships between jobs. Let's hover a cursor on <i>deploy dev</i> job:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqdhh_geQw3dsylidLlMzvsazHczxqpC-CvRNs7Jt5B_eLC3gle53LBA9ajBxLxkquKaq8d1vpPJbbe0Pg5lbEw7u8e22zYpuvc4vtPJY8fKtzNkk4UqMYfuhO8yht5r6f1sUjl8T9ls0/s1426/Screen+Shot+2021-02-09+at+10.09.39.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="416" data-original-width="1426" height="186" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqdhh_geQw3dsylidLlMzvsazHczxqpC-CvRNs7Jt5B_eLC3gle53LBA9ajBxLxkquKaq8d1vpPJbbe0Pg5lbEw7u8e22zYpuvc4vtPJY8fKtzNkk4UqMYfuhO8yht5r6f1sUjl8T9ls0/w640-h186/Screen+Shot+2021-02-09+at+10.09.39.png" width="640" /></a></div><p style="text-align: left;"><i>Deploy dev</i> job needs only <i>build</i> job. And you can easily see the relationship between these two jobs. <i>deploy dev</i> job runs as soon as <i>build</i> job finishes without waiting for jobs in <i>test</i> stage to finish. So, I want to deploy my changes on <i>dev</i> environment as soon as possible 😉.</p><p style="text-align: left;">Now, let's hover a cursor on <i>deploy prod</i> job:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFyyvCxCC6ajPVzIQFi4oj5nX0Leqv94QgdYhVGIJB6hmCqZ_miZ-2CHiPHwbG4DjCe8mLTzBLrvFVxUxPcTyNlFRTosC3APoTIpplG8GAZNH18yAaSVhn-ygL13IthXGkc_zssUxp2sg/s986/Screen+Shot+2021-02-09+at+10.18.28.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="438" data-original-width="986" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFyyvCxCC6ajPVzIQFi4oj5nX0Leqv94QgdYhVGIJB6hmCqZ_miZ-2CHiPHwbG4DjCe8mLTzBLrvFVxUxPcTyNlFRTosC3APoTIpplG8GAZNH18yAaSVhn-ygL13IthXGkc_zssUxp2sg/w640-h284/Screen+Shot+2021-02-09+at+10.18.28.png" width="640" /></a></div><p style="text-align: left;">Here, the situation is different. I don't want to deploy something on production without any tests! So, <i>deploy prod</i> job needs these three guys from <i>test</i> stage. <i>Deploy prod</i> job runs as soon as these three <i>test</i> jobs finish. Do you agree that such visualization is useful?</p><h2 style="text-align: left;">include keyword</h2><p style="text-align: left;">What if you have a more complicated pipeline? Pipeline Editor supports <i><a href="https://swiety-python.blogspot.com/2020/11/gitlab-include-keyword.html" target="_blank">include</a></i> keyword, so you can use it. Let's say that you split the stages and put each stage into another yaml file. Then you can just include all these files:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9aSWMYd5QT4u0BS59iyF4bgAvr5i4h87SbY3Ji6Kw1VTzycRSUh-plKogbP2U6l0rywcCBrxm_0ihQnE1hMXRzm5tCFfDPtlNxrL3k8TS7XtZCb41creH7XT0iAH9cPqH7S6J5I4hU04/s388/Screen+Shot+2021-02-09+at+11.35.04.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="216" data-original-width="388" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9aSWMYd5QT4u0BS59iyF4bgAvr5i4h87SbY3Ji6Kw1VTzycRSUh-plKogbP2U6l0rywcCBrxm_0ihQnE1hMXRzm5tCFfDPtlNxrL3k8TS7XtZCb41creH7XT0iAH9cPqH7S6J5I4hU04/s320/Screen+Shot+2021-02-09+at+11.35.04.png" width="320" /></a></div><p style="text-align: left;">And your Pipeline Editor still is able to function normally, because as I said earlier it supports this keyword.</p><p style="text-align: left;">Of course in this way you cannot test everything. For example, you cannot test if your <i>ifs</i> in <i>rules</i> keyword are correct, etc. But, it's still useful. If you just need to add small fix, it might be a good alternative to Merge Request.</p><p style="text-align: left;">Here is the video if you prefer this way of learning:</p><p style="text-align: left;"></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="334" src="https://www.youtube.com/embed/LfTYQD1V800" width="559" youtube-src-id="LfTYQD1V800"></iframe></div><p></p><p></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-71879183742864169142021-02-05T21:26:00.005+01:002021-02-08T09:10:23.967+01:00Pluto - how to find deprecated Kubernetes apiVersions<p>Hi, today I am going to introduce <a href="https://pluto.docs.fairwinds.com/" target="_blank">Pluto</a> to you. So, what is it? 🤔<br /></p><p></p><blockquote>Pluto is a utility to help users find deprecated Kubernetes apiVersions in their code repositories and their helm releases.</blockquote><p></p><p>Sounds great! You use Kubernetes right? So, you know that Kubernetes has <a href="https://kubernetes.io/docs/reference/using-api/deprecation-policy/" target="_blank">Deprecation Policy</a>. It is a large system, so many features evolve over time and many of them might be removed. Thus, to avoid problems, Kubernetes provides a deprecation policy. So, you know in advance that something is going to be deprecated and you should act!</p><p>There are 3 main "<a href="https://kubernetes.io/docs/reference/using-api/#api-versioning" target="_blank">API Groups</a>" in Kubernetes:</p><ul style="text-align: left;"><li>Alpha (v1alpha1)<br /></li><li>Beta (v1beta1)</li><li>Stable (v1)</li></ul><p>From time to time, some objects (or their versions) in Kubernetes can be removed or deprecated and it would be nice to know about it in advance. And for that job you can use:</p><p></p><div class="separator" style="clear: both; text-align: center;"><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjm8k_deNa5-CgSGxpRNgBHJ6meq7FhbsA2umR5p37u5bhGhdLz8hg8YEqVP9pF24scRAYoNoS89ccdn0TS5hcB0WV_2jh-vQpa8lT-9XGTeDJ75ofzAyx0CVtBJTbh7y4ApdGx0bG6nO0/s914/pluto-logo.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="914" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjm8k_deNa5-CgSGxpRNgBHJ6meq7FhbsA2umR5p37u5bhGhdLz8hg8YEqVP9pF24scRAYoNoS89ccdn0TS5hcB0WV_2jh-vQpa8lT-9XGTeDJ75ofzAyx0CVtBJTbh7y4ApdGx0bG6nO0/w400-h166/pluto-logo.png" width="400" /></a></p></div>Let's see this guy in an action!<p></p><p>You need to install it firstly of course. You can do that by this command:</p>
<script src="https://gist.github.com/tomislater/24872486fa195289ea6162f273b559be.js"></script>
<p>I used <a href="https://minikube.sigs.k8s.io/docs/start/" target="_blank">minikube</a> (<span style="background-color: #eeeeee;"><i>minikube start --kubernetes-version=v1.16.1</i></span>) to set up a local Kubernetes cluster, so by default there are only some basic stuff. I am going to install <a href="https://github.com/helm/charts/tree/master/stable/grafana" target="_blank">grafana</a> via <a href="https://helm.sh/" target="_blank">Helm</a>. This chart is already deprecated, so it is useful, because you find some old stuff there!</p>
<script src="https://gist.github.com/tomislater/47dc8d36e9c6fe3f2c7cca4519b98d99.js"></script>
<p>The chart has been installed. Let's run <i>pluto</i>. You can use <i>pluto</i> in three ways:</p><ul style="text-align: left;"><li>File Detection in a Directory</li><li>Helm Detection (in-cluster)</li><li>Helm Chart Checking (local files)</li></ul><p>I am going to use Helm Detection (in-cluster) because my chart is installed and I want to check it. By default <i>pluto</i> checks <span style="background-color: #eeeeee;"><i>v1.16.0</i></span> version, but there is a flag <span style="background-color: #eeeeee;"><i>--target-versions</i></span> and I can check whatever version I want. So, let's say that I have Kubernetes cluster in <span style="background-color: #eeeeee;"><i>1.16</i></span> version and I want to upgrade my cluster to <span style="background-color: #eeeeee;"><i>1.17</i></span>.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMAUXo1nSLASOI31QFdOXTPWALWd0RsxyCGSmO1eNafVkWGd6Dx5bKjfUJyAf89eUPO6iFK0xbpfujbqvyC_pan5XxuzIVYBExK4GTQrH1OX0zhRsMkoJVVw2nUT8vOaRsJwPp8Oko3ec/s245/k8s-upgrade-to-1-17.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="102" data-original-width="245" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMAUXo1nSLASOI31QFdOXTPWALWd0RsxyCGSmO1eNafVkWGd6Dx5bKjfUJyAf89eUPO6iFK0xbpfujbqvyC_pan5XxuzIVYBExK4GTQrH1OX0zhRsMkoJVVw2nUT8vOaRsJwPp8Oko3ec/s16000/k8s-upgrade-to-1-17.png" /></a></div><p></p><p>Before that, I want to check if everything will be all right with my deployments after upgrade. I can type <span style="background-color: #eeeeee;"><i>pluto detect-helm -o wide --target-versions k8s=v1.17.0</i></span>:<br /></p><p></p><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq77CselBnrtuaFD4htbw_p-jq56dR5sM3VUFRDOffsOr5ufCItoD5m-B3o_PiDaKNeWPJX-jCBNp-CirGdlVUwvM7ouIGSChKlNUA_f4_hMaGzfYuwL-tSDyE7_IZl057U1h_VM3HrmA/s1377/pluto-detect-helm.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="368" data-original-width="1377" height="172" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq77CselBnrtuaFD4htbw_p-jq56dR5sM3VUFRDOffsOr5ufCItoD5m-B3o_PiDaKNeWPJX-jCBNp-CirGdlVUwvM7ouIGSChKlNUA_f4_hMaGzfYuwL-tSDyE7_IZl057U1h_VM3HrmA/w640-h172/pluto-detect-helm.png" width="640" /></a></p></div><p>What I see here? <i>pluto</i> tells me that in <span style="background-color: #eeeeee;"><i>v1.17.0</i></span> version, <span style="background-color: #eeeeee;"><i>rbac.authorization.k8s.io/v1beta1</i></span> will be deprecated for <span style="background-color: #eeeeee;"><i>Role</i></span> and <span style="background-color: #eeeeee;"><i>RoleBinding</i></span> objects. I can still use them (in <span style="background-color: #eeeeee;"><i>v1.17.0</i></span> version), but it will be removed in <span style="background-color: #eeeeee;"><i>v1.22.0</i></span> version. So, after the upgrade I should change <span style="background-color: #eeeeee;"><i>rbac.authorization.k8s.io/v1beta1</i></span> to <span style="background-color: #eeeeee;"><i>rbac.authorization.k8s.io/v1</i></span> for these objects in my helm chart.</p><p>I also checked <span style="background-color: #eeeeee;"><i>v1.22.0</i></span> version. And, now I know that I should change my <span style="background-color: #eeeeee;"><i>Ingress</i></span> object for grafana too. The current version will be deprecated in <span style="background-color: #eeeeee;"><i>v1.19.0</i></span>, and I should use <span style="background-color: #eeeeee;"><i>networking.k8s.io/v1</i></span> instead.</p><p>Great, I can check what's going on on my cluster, but I also want to know about it in my CI tools. For this, I can use <i>Helm Chart Checking (local files)</i>. Here is an example:</p><div class="separator" style="clear: both; text-align: center;"><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjYd-55NBeR3TZcatjJeX_UuVbBQCUcp_3RlDIyIDmG6WQHmmOshyPrqhn9yifGKNIGE9kiFAVdWuBz-Cmtb7HJzPc_r0qQqT6nxlrUxmLawmAHlTD8gTkRotD04v9QFxzY3XAgOVA-bw/s1122/pluto-ci.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="180" data-original-width="1122" height="102" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjYd-55NBeR3TZcatjJeX_UuVbBQCUcp_3RlDIyIDmG6WQHmmOshyPrqhn9yifGKNIGE9kiFAVdWuBz-Cmtb7HJzPc_r0qQqT6nxlrUxmLawmAHlTD8gTkRotD04v9QFxzY3XAgOVA-bw/w640-h102/pluto-ci.png" width="640" /></a></p></div><p>I rendered the template of the <span style="background-color: #eeeeee;"><i>chronograf</i></span> chart and piped it into <i>pluto</i>. And this way I know that I cannot use this chart in Kubernetes <span style="background-color: #eeeeee;"><i>1.16</i></span>. Because <span style="background-color: #eeeeee;"><i>extensions/v1beta1</i></span> has been removed in <span style="background-color: #eeeeee;"><i>v1.16.0</i></span>, but it is still in my chart.</p><p>So, in this way you can loop through your helm charts and check them with <i>pluto</i> to find out about potential problems in advance.</p><p>That's it. Have a nice day!</p><p>If you do not want to read this post, here is a video:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="332" src="https://www.youtube.com/embed/ggUsaywRd44" width="521" youtube-src-id="ggUsaywRd44"></iframe></div><p></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-29064597694057734262021-02-02T18:20:00.008+01:002021-02-05T21:26:50.029+01:00Mozilla SOPS: Secrets OPerationS<p style="text-align: left;">I wrote a blog post about <a href="https://swiety-python.blogspot.com/2021/01/managing-secrets-in-gitlab.html" target="_blank">managing secrets in GitLab / Git</a> some time ago, where I touched <a href="https://github.com/mozilla/sops" target="_blank">sops</a>. Today, I am going to write more about this tool.</p><p style="text-align: left;"><i>sops</i> is useful when you want to encrypt your <i>data</i> and keep it somewhere securely. Why is it so secure? Because it uses <a href="https://swiety-python.blogspot.com/2021/01/aws-kms-basic-concepts.html#envelop-encryption" target="_blank">envelope encryption</a>. This way you can keep your encrypted <i>data</i> and encrypted <i>data key</i> (which is needed to decrypt your <i>data</i>) in the same file. So, when everything is encrypted you can store it anywhere, for example in your git repository.</p><h3 style="text-align: left;">How it works?</h3><p style="text-align: left;"><i>sops</i> generates a <i>data key</i> and this <i>data key</i> is used to encrypt and decrypt your <i>data</i>. So, how then is your <i>data key</i> encrypted? 🤔 By your KMS or PGP master key (or both of them, or even more... <i>sops</i> supports <a href="https://swiety-python.blogspot.com/2021/01/aws-kms-basic-concepts.html" target="_blank">AWS KMS</a>, GCP KMS, Azure Key Vault and PGP).</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhqEoqiNdCmCoGPU_lYHulq_qUKtvJbgJGLPF2mn_JcPmQia6ZG4k5I5NA-l4EJ85asHMQ8ETitF8YVXmlwDe3xiq8I1BQE9TT_OsVSBrapWhKP-aubfqYoL3GNCasaEx26CBpyJ5OSqo/s613/key-hierarchy-cmk-2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="219" data-original-width="613" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhqEoqiNdCmCoGPU_lYHulq_qUKtvJbgJGLPF2mn_JcPmQia6ZG4k5I5NA-l4EJ85asHMQ8ETitF8YVXmlwDe3xiq8I1BQE9TT_OsVSBrapWhKP-aubfqYoL3GNCasaEx26CBpyJ5OSqo/s16000/key-hierarchy-cmk-2.png" /></a></div><p style="text-align: left;">As you can see, <i>sops</i> only touches your <i>data key</i> and your <i>data</i>. With your <i>master/wrapping key</i> you encrypt and decrypt your <i>data key</i>.</p><p style="text-align: left;">By default, you can encrypt and decrypt your <i>data key</i> by each <i>master key</i>. But, you can use <i>key groups</i> if you want to be more secure (<i>sops</i> uses <a href="https://en.wikipedia.org/wiki/Shamir's_Secret_Sharing" target="_blank">Shamir's Secret Sharing</a> algorithm under the hood to split the <i>data key</i>). Using <i>key groups</i>,<i> sops</i> splits <i>data key</i> into parts. Each <i>key group</i> encrypts and decrypts only a part of <i>data key</i>:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1xIfcIoPjsw2UrFk5J8M0KkSIG81NhOiAp4fPRDq9D3kcMmEtdKYzRZSkckvnN7HT6iadAUbg6d8sWFtS2REciRn3wJ4BrV2ShfFv1vwzoGu1AM7MA4DCVtJft-LMe2OjQf6UQf45cVs/s440/key-groups-2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="345" data-original-width="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1xIfcIoPjsw2UrFk5J8M0KkSIG81NhOiAp4fPRDq9D3kcMmEtdKYzRZSkckvnN7HT6iadAUbg6d8sWFtS2REciRn3wJ4BrV2ShfFv1vwzoGu1AM7MA4DCVtJft-LMe2OjQf6UQf45cVs/s16000/key-groups-2.png" /></a></div><p style="text-align: left;">So, what is going on here? There are three <i>key groups</i>. Each of them encrypts and decrypts only a part of <i>data key</i>. In order to decrypt a file, you must decrypt <i>data key</i> firstly, and each <i>key group</i> can decrypt only a part of it. So, to decrypt the <i>data key</i> you have to decrypt all the parts (by default) and then merge them (it is not exactly merging, but you get the idea). Each <i>master key</i> in the <i>key group</i> can decrypt a part of the <i>data key</i>. So, in this example, in <i>key group 1</i>, you need to have access to only one of the <i>master keys</i> to decrypt this part of <i>data key</i>. The same is for another <i>key groups</i>. You only need one <i>master key</i> within each <i>key group</i> to decrypt this part of <i>data key</i>.<br /></p><p style="text-align: left;">You can also create five <i>key groups</i> and set threshold to 3. This way, you only need access to any 3 <i>key groups</i> to decrypt a file (Shamir's Secrets Sharing algorithm).</p><p style="text-align: left;">As I wrote, <i>key groups</i> are optional and in most cases you do not need to use them.</p><h3 style="text-align: left;">Key rotation</h3><p style="text-align: left;">If you think, that your <i>data key</i> leaked you can renew your <i>data key</i>; it is also a good practice to do that on a regular basis. <i>Sops</i> will create a new <i>data key</i>, and encrypt <i>data</i> with this new <i>data key</i>. You just need to type: <i style="background-color: #eeeeee;">sops -r file-with-secrets.yaml</i>.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzSaTGl8Z4RT-u0PlhQ2IQg5JUU3We66sqflxufvZDoBXd4w4TXsl9fUsbaf1qNRX21lncLPDtWJtEe-Aw4vWMgCWCstoc2vHu_NRGg4Ac39AFVjO3kSCMApZf_RpL_We5SVDSbMHZY8k/s399/rotation.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="100" data-original-width="399" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzSaTGl8Z4RT-u0PlhQ2IQg5JUU3We66sqflxufvZDoBXd4w4TXsl9fUsbaf1qNRX21lncLPDtWJtEe-Aw4vWMgCWCstoc2vHu_NRGg4Ac39AFVjO3kSCMApZf_RpL_We5SVDSbMHZY8k/s16000/rotation.png" /></a></div><h3 style="text-align: left;">Examples</h3><p style="text-align: left;">Okay, I think that you understand basics. Now let's see some examples.</p><h4 style="text-align: left;">First example</h4><p style="text-align: left;">In the first example I am going to use my PGP key.</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='535' height='309' src='https://www.blogger.com/video.g?token=AD6v5dwmX8T59TIVK-eHpHOeLakButgNJVcyPI4lNZK6pvPb23Nohq_TF6ytPhwM237StuJ4L_P2lSFG0_CVaSeFiA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><p style="text-align: left;">I added my fingerprint (from PGP key) to <i>.sops.yaml</i> file. This way I tell <i>sops</i> that I want to encrypt and decrypt <i>data key</i> with this <i>master key</i>. <i>Sops</i> encrypted the <i>data key</i> with my public key, and <i>data</i> in a file has been encrypted by <i>data key</i>. Only someone who has access to my private key can decrypt the <i>data key</i>, and then using this <i>data key</i>, the <i>data</i> can be decrypted.</p><h4 style="text-align: left;">Second example</h4><p style="text-align: left;">In this example, I am going to use two PGP keys. Let's say that I'm working with someone and I have access to their public key.</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='529' height='319' src='https://www.blogger.com/video.g?token=AD6v5dzdQuCZI9qabXxkwMUWMdWW1d6Iqg6s3aQV5RbkS4vXbzETtQBGoXCZxTkULWQRGc1e7pFk51330grGs8aozA' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><p style="text-align: left;">Using two PGP keys, the same <i>data key</i> is encrypted two times (separately). Once by the public key of my colleague and once by mine. Thus, there are two records in <i>sops.pgp</i> section. To decrypt the file I only need my private key. The same way, if my colleague needs to decrypt the file, they only need their private key.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNCYV15gBm8Vb_EZPtCooY5z2YQWlsE2NncB5H-j6OcFwPdv1g-LA-GwhRTYiyfh4WMgs1KiEicOiXL8O3NLiRfLkBLsGn_DNlayB2v0NSGF3LWuwrPrFUy8iePh4vEdhiMcHN31sOwOc/s391/public-key-to-encrypted.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="165" data-original-width="391" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNCYV15gBm8Vb_EZPtCooY5z2YQWlsE2NncB5H-j6OcFwPdv1g-LA-GwhRTYiyfh4WMgs1KiEicOiXL8O3NLiRfLkBLsGn_DNlayB2v0NSGF3LWuwrPrFUy8iePh4vEdhiMcHN31sOwOc/s16000/public-key-to-encrypted.png" /></a></div><p style="text-align: left;">In this way, if I die (or lose my private key...), my colleague still will be able to decrypt the file. So, it is always a good idea to encrypt your <i>data key</i> with more <i>master keys</i> than one ☝️.</p><h4 style="text-align: left;">Third example</h4><p style="text-align: left;">In this example, I am going to show you <i>key groups</i>. There will be three <i>key groups</i> with one <i>master key</i> in each of them.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnishzkOjOoxWOCxd3pL1rHKxbwHu9MJvtYFN9hihj5vr4WNWmMdC_s8QyD2P9vTGhTF5tH1nV_P3xXvsi1eHRVWNnqOJzRr0Mo2LMdTdFOPqXm-_ELoVpHqo7vl4NMU8OE3WM0S9YMGM/s440/key-groups-threshold-2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="345" data-original-width="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnishzkOjOoxWOCxd3pL1rHKxbwHu9MJvtYFN9hihj5vr4WNWmMdC_s8QyD2P9vTGhTF5tH1nV_P3xXvsi1eHRVWNnqOJzRr0Mo2LMdTdFOPqXm-_ELoVpHqo7vl4NMU8OE3WM0S9YMGM/s16000/key-groups-threshold-2.png" /></a></div><p style="text-align: left;">Let's say that we have three developers. And, to decrypt the file we need at least two of them. I can set threshold to 2 (<a href="https://github.com/mozilla/sops#key-groups" target="_blank">by default, it would be 3</a>). So, in order to decrypt <i>data key</i>, we need at least two different developers (I assume that each developer has access to only his private key).</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='526' height='323' src='https://www.blogger.com/video.g?token=AD6v5dwHt-_2bxk2a_FpGiz73knGdkGQye2w0nPyzAzHy_dPWCz60sE1qGK_ZOPdxquQeGpHyRmceknYrVj2fT2rSg' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div><p style="text-align: left;">In this way, in order to decrypt the file there always must meet at least two developers. So, it might be useful when you have something very very secret 🤫. There is a limit for 256 <i>key groups</i> 😅.</p><h3 style="text-align: left;">Decryption</h3><p style="text-align: left;">Okay, so I have the encrypted file. I can type <i style="background-color: #eeeeee;">sops example-secret.yaml</i> and see the content. But, what if I want to use it in my scripts? And I need access to a specific key.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0ZChz6tnpoVZVCX9YOHbjA514BBB40V5hKGmBPsinDjwPsWVphsKnZLLwRQxEmA4c2orLu9KfuTw7xd1rGDW73t6Lm2g4LMHgn296L9iLPleECt9ba2JlHDxo-PkACwag50V5nQsR7Fs/s1618/Screenshot+2021-02-02+at+16.29.31.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1100" data-original-width="1618" height="435" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0ZChz6tnpoVZVCX9YOHbjA514BBB40V5hKGmBPsinDjwPsWVphsKnZLLwRQxEmA4c2orLu9KfuTw7xd1rGDW73t6Lm2g4LMHgn296L9iLPleECt9ba2JlHDxo-PkACwag50V5nQsR7Fs/w640-h435/Screenshot+2021-02-02+at+16.29.31.png" width="640" /></a></div><p style="text-align: left;">Let's say that I want to get a password for redis and put it to an environment variable. How can I do that? Firstly, I have to tell <i>sops</i> that I want to decrypt the file (<i style="background-color: #eeeeee;">--decrypt</i> flag). Secondly, I need to provide what exactly I want to extract (<i style="background-color: #eeeeee;">--extract</i> flag):</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbg1wUh9lGXHMeAtMaCMYDK8PF2U7bO0Gr3zPLxFsezvD6aRt1nxsb1kIge9mk70txXGVTpU1NyfkuOg7FY77PZjJPsk6BC98To4_If4sBYs3lrhtf3lrml9yWNL7nvhuRrkL_KuZGLQo/s1364/Screenshot+2021-02-02+at+16.36.17.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="630" data-original-width="1364" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbg1wUh9lGXHMeAtMaCMYDK8PF2U7bO0Gr3zPLxFsezvD6aRt1nxsb1kIge9mk70txXGVTpU1NyfkuOg7FY77PZjJPsk6BC98To4_If4sBYs3lrhtf3lrml9yWNL7nvhuRrkL_KuZGLQo/w640-h296/Screenshot+2021-02-02+at+16.36.17.png" width="640" /></a></div><p style="text-align: left;">So, when I know how to extract a specific value, then I can put it to my environment variable:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5_lx2Gc-N4zmn9mxf1-lB-PANi81DUdFITIHnEFpYki9jOOwRg1hFLstZe9NO3lhTj_e63CAvUI6YAS3xVKtWP-dseLQcWDqYWlHbTIyEtWLiubs_hVT-DDDQa8BB1Oa-f9m9MgAi8lA/s1630/Screenshot+2021-02-02+at+16.40.10.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="664" data-original-width="1630" height="260" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5_lx2Gc-N4zmn9mxf1-lB-PANi81DUdFITIHnEFpYki9jOOwRg1hFLstZe9NO3lhTj_e63CAvUI6YAS3xVKtWP-dseLQcWDqYWlHbTIyEtWLiubs_hVT-DDDQa8BB1Oa-f9m9MgAi8lA/w640-h260/Screenshot+2021-02-02+at+16.40.10.png" width="640" /></a></div><p style="text-align: left;">And that's it! There is a lot more stuff you can do with <i>sops</i> of course. But, my goal in this post was to introduce this tool to you. And I hope, I achieved that. Let me know if it was useful!</p><p style="text-align: left;">And here is a video if you do not want to ready this post:</p><p style="text-align: left;"></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="371" src="https://www.youtube.com/embed/DWzJ87KbwxA" width="560" youtube-src-id="DWzJ87KbwxA"></iframe></div><br /> <br /><p></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-47066787905026364812021-01-26T20:14:00.005+01:002021-02-02T16:49:31.878+01:00AWS KMS - Basic concepts<p style="text-align: left;">Firstly what is it?</p><p style="text-align: left;"></p><blockquote><p style="text-align: left;">AWS Key Management Service (AWS KMS) is a managed service that makes it easy for you to create and control customer master keys (CMKs), the encryption keys used to encrypt your data. AWS KMS CMKs are protected by hardware security modules (HSMs) that are validated by the FIPS 140-2 Cryptographic Module Validation Program except in the China (Beijing) and China (Ningxia) Regions.</p><p style="text-align: left;"></p></blockquote><p style="text-align: left;">So, with AWS KMS you can store your customer master keys securely.</p><h3 style="text-align: left;">What are customer master keys (CMKs) then?</h3><p>Customer master key is the primary resource in AWS KMS (so, it has own ARN). It is a logical representation of a master key. You can create <i>symmetric</i> and <i>asymmetric</i> CMKs. CMKs never leave AWS infrastructure unencrypted. No one from AWS has access to these guys, only you. Your master keys are stored in such devices (hardware security module (<a href="https://en.wikipedia.org/wiki/Hardware_security_module" target="_blank">HSM</a>)):</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsGPYWHLLpSt8zEeNSTgA9vbd0QPxO21uiKd1NCNAfJhiugnGplzV2Hz-r9E-3-boCOY9jjq0EvUosASEhHMilQp52OxReXWooGEY-Lrc1KFN0497D5867utTenme0rb6nkIpnFMyycJM/s667/IBM4767.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="418" data-original-width="667" height="251" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsGPYWHLLpSt8zEeNSTgA9vbd0QPxO21uiKd1NCNAfJhiugnGplzV2Hz-r9E-3-boCOY9jjq0EvUosASEhHMilQp52OxReXWooGEY-Lrc1KFN0497D5867utTenme0rb6nkIpnFMyycJM/w400-h251/IBM4767.png" width="400" /></a></div><p>You can read more about the cryptographic details <a href="https://docs.aws.amazon.com/kms/latest/cryptographic-details/intro.html" target="_blank">here</a>.</p><p>Okay, so what can you do with AWS KMS?</p><h3 style="text-align: left;">AWS managed CMKs</h3><p>Using <a href="https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#aws-managed-cmk" target="_blank">AWS managed CMKs</a> you can encrypt/decrypt your data stored on <a href="https://docs.aws.amazon.com/kms/latest/developerguide/services-s3.html" target="_blank">S3</a> or <a href="https://docs.aws.amazon.com/kms/latest/developerguide/services-ebs.html" target="_blank">EBS</a> (it is handled transparently and requires no additional action from you!). If someone steals your (encrypted) data, without access to AWS KMS they can do nothing with this information. <a href="https://aws.amazon.com/kms/features/#AWS_Service_Integration" target="_blank">Here</a> is a list of AWS Services integrated with AWS KMS.</p><h3 style="text-align: left;">Customer managed CMKs</h3><p>You can also use <a href="https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#customer-cmk" target="_blank">Customer managed CMKs</a>, and integrate it with <a href="https://swiety-python.blogspot.com/2021/01/managing-secrets-in-gitlab.html#sops" target="_blank">sops</a> for example. You can generate, encrypt and decrypt data keys using AWS KMS, but you are not forced to generate data keys in AWS KMS. If your tool generates data keys (like <i>sops</i>) you can use AWS KMS to encrypt/decrypt these keys. No problem. But, you have to remember that after creation of data key by AWS KMS, you must care about this key since then. AWS KMS cares only about master keys.</p><h3 style="text-align: left;">Data keys</h3><p style="text-align: left;">Okay... so what are these data keys? 🤔</p><p></p><blockquote>Data keys are encryption keys that you can use to encrypt data, including large amounts of data and other data encryption keys.</blockquote><p></p><p>So... basically, with data key you can encrypt and decrypt your data (master key encrypts/decrypts only data key, not data!).</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9kPYB-GH7xtjLJYBjEDL7hz4Owfdxl80wWxPEJxq681ClgdqYzGFsQh_UFltYUwawQvJked07W5zTexojWM0ktrZVajceHd6C__FHe_kOyhkUdYcVz5rkzEwistrrPqXuvcJjalib-gE/s546/generate-data-key.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="546" data-original-width="426" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9kPYB-GH7xtjLJYBjEDL7hz4Owfdxl80wWxPEJxq681ClgdqYzGFsQh_UFltYUwawQvJked07W5zTexojWM0ktrZVajceHd6C__FHe_kOyhkUdYcVz5rkzEwistrrPqXuvcJjalib-gE/w313-h400/generate-data-key.png" width="313" /></a></div><p>As you can see, AWS KMS can generate data key for you. You will receive plaintext data key and encrypted data key. You can store encrypted data key wherever you want because (you know) it is encrypted and can be decrypted only by your CMK (Customer Master Key), so do not share your AWS credentials for CMK 🙏.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZa5tgwy91ifW6v-c1ihlrebW46JziIgEKp2heCr2SodViy48kSnet3j9EwKcdvJy7mPZoWON41Y6rN0cobWfKTOurikbiM7T7yi9U0nfYVqT9qAhFAGcKbOBrNXtEYBKXKustuPGn8p4/s479/encrypt-with-data-key.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="289" data-original-width="479" height="241" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZa5tgwy91ifW6v-c1ihlrebW46JziIgEKp2heCr2SodViy48kSnet3j9EwKcdvJy7mPZoWON41Y6rN0cobWfKTOurikbiM7T7yi9U0nfYVqT9qAhFAGcKbOBrNXtEYBKXKustuPGn8p4/w400-h241/encrypt-with-data-key.png" width="400" /></a></div><p>With plaintext data key you can encrypt/decrypt your data and you should <strike>throw away this data key through window</strike> destroy this key as soon as possible. You should not store this data key anywhere. You need this data key in plaintext form only for a short period of time.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKAp4r_8_E1S-h0RvTpQ19JJunYRRAxlVn7WpXOPWZ112OSam_7_Vk7iyhX52hAtB9KW4pIQGUU0XW-gzh41Fql6Ry0AHDhdrIHed7WnZK0wlRlhoxQEnWdaaPLsbzE8EhZH8UGUwPHdg/s564/decrypt.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="366" data-original-width="564" height="260" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKAp4r_8_E1S-h0RvTpQ19JJunYRRAxlVn7WpXOPWZ112OSam_7_Vk7iyhX52hAtB9KW4pIQGUU0XW-gzh41Fql6Ry0AHDhdrIHed7WnZK0wlRlhoxQEnWdaaPLsbzE8EhZH8UGUwPHdg/w400-h260/decrypt.png" width="400" /></a></div><p>If you want to decrypt your data, you have to decrypt your encrypted data key firstly. How to do that? You need to send your encrypted data key to AWS KMS, AWS uses your CMK, decrypts this encrypted data key and sends you the data key in plaintext form. Now, you can decrypt/encrypt your data using this key. Remember that you should remove this data key (plaintext 😱) as soon as possible.</p><p>You never have access to your CMK, I mean, your CMK never leaves AWS infrastructure. You cannot "get" your CMK (that's why it is so secure). You only need to send data key to AWS KMS. This way the whole operation is very fast, you do not have to send the whole data. Encryption/decryption of data is on your side. It is called envelope encryption.</p><h3 id="envelop-encryption" style="text-align: left;">Envelope encryption</h3><p></p><blockquote>Envelope encryption is the practice of encrypting plaintext data with a data key, and then encrypting the data key under another key.</blockquote><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj56x_iG6gJtLMHSTResxPi_nELa7DUarutvY5b8uvUAs2OdnZA8TN8zoPUW3t4LazLrOlLiVk2qA4W_UhcgpaCkdpqKccO5ADovvtjoeyxMEtoR9MVHQzkqvm3R7GrWugksd8otmmKO94/s613/key-hierarchy-cmk.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="219" data-original-width="613" height="143" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj56x_iG6gJtLMHSTResxPi_nELa7DUarutvY5b8uvUAs2OdnZA8TN8zoPUW3t4LazLrOlLiVk2qA4W_UhcgpaCkdpqKccO5ADovvtjoeyxMEtoR9MVHQzkqvm3R7GrWugksd8otmmKO94/w400-h143/key-hierarchy-cmk.png" width="400" /></a></div><p>This way you can store your data key (encrypted of course...) securely where you want. The data key is inherently protected by encryption (your CMK has encrypted it, remember?). Because encryption can be time consuming (if you have a lot of data) it makes more sense to re-encrypt the data key instead of raw data. That's another benefit of envelope encryption.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhZoovqSct6nbzUf8Lx6-T8RAKgcWfUn4g9uX65mez1YOxr5sAQn1sUAbK7ReuwPkx59i-zMVnDJXOVGojnC9rIv_RtTom6ayL_2MjecOpC4khMFoDjru-et6MsyFBLVwj3SIF8gQ2UsA/s1000/s-l1000.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="665" data-original-width="1000" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhZoovqSct6nbzUf8Lx6-T8RAKgcWfUn4g9uX65mez1YOxr5sAQn1sUAbK7ReuwPkx59i-zMVnDJXOVGojnC9rIv_RtTom6ayL_2MjecOpC4khMFoDjru-et6MsyFBLVwj3SIF8gQ2UsA/w400-h266/s-l1000.jpg" width="400" /></a></div><p>You can also encrypt the data encryption key by another encryption key, and so on... But, eventually, there must be one key in plaintext so you can decrypt the keys and then your data. And this top-level plaintext key is known as the <i>wrapping/master key</i>.</p><h3 style="text-align: left;">Data key pairs</h3><p></p><blockquote>Data key pairs are asymmetric data keys that consist of a mathematically-related public key and private key.</blockquote><p>Using data key pairs, you get public key, private key in plaintext form and encrypted private key:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpRWpUyKzV4Ed5wuszbb-8eKjDMLnQJwwaA3gJ5MkesOUjs5Ii7K2DX6g_MhcsB6Vya8vpXHm19oFzFewKpijiZ0mrCDjIVowPATDKkTochXWs1lUdRmhHlgYw09VSHTayP-L08Z3kQm0/s661/generate-data-key-pair.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="661" data-original-width="496" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpRWpUyKzV4Ed5wuszbb-8eKjDMLnQJwwaA3gJ5MkesOUjs5Ii7K2DX6g_MhcsB6Vya8vpXHm19oFzFewKpijiZ0mrCDjIVowPATDKkTochXWs1lUdRmhHlgYw09VSHTayP-L08Z3kQm0/w300-h400/generate-data-key-pair.png" width="300" /></a></div><p>When you want to encrypt your data you need to use public key. In most cases, data key pairs are useful when many parties need to encrypt data that only the party that holds the private key can decrypt.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVXGgNcRhSJrmnJvc6us6cEc5V3JxppZwhCp6bU7GN9QFu12qsZDNS_HEZNNyqAQ1SWwFOKu53T5uJJlNEOwyO0GGVx4zewDcZ3f730HRnGNBjLl87YCC3Qi1BEYxqcUIPYVIwpzz8UEU/s500/encrypt-with-data-key-pair.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="289" data-original-width="500" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVXGgNcRhSJrmnJvc6us6cEc5V3JxppZwhCp6bU7GN9QFu12qsZDNS_HEZNNyqAQ1SWwFOKu53T5uJJlNEOwyO0GGVx4zewDcZ3f730HRnGNBjLl87YCC3Qi1BEYxqcUIPYVIwpzz8UEU/w400-h231/encrypt-with-data-key-pair.png" width="400" /></a></div><p>What if someone encrypted something by your public key and now you want to decrypt this data? You only have your encrypted private key. So, for obtaining the private key in plaintext form you must send the encrypted private key to AWS KMS. It will decrypt the encrypted private key and send you the plaintext private key.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbkDuFGrWuAWUWWSR7djn3SCQyZmfMtFaXkvl2sd8mhyphenhyphenhcRbbpIDU0mOQD6m7RSpXhihOGvR6iyQ6ne2275TIVBjDcDcDUtDyGq307GesKIZBKdEeR6gE5A4tWHrXNLT68FM_Tfil6Lrw/s529/decrypt-with-data-key-pair.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="340" data-original-width="529" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbkDuFGrWuAWUWWSR7djn3SCQyZmfMtFaXkvl2sd8mhyphenhyphenhcRbbpIDU0mOQD6m7RSpXhihOGvR6iyQ6ne2275TIVBjDcDcDUtDyGq307GesKIZBKdEeR6gE5A4tWHrXNLT68FM_Tfil6Lrw/w400-h258/decrypt-with-data-key-pair.png" width="400" /></a></div><p>And now, you can decrypt your data and remove the plaintext private key as soon as possible! 👏</p><h3 style="text-align: left;">Digital signatures <br /></h3><p>What can you also do with data key pairs? You can use them for <a href="https://en.wikipedia.org/wiki/Digital_signature" target="_blank">digital signatures</a>. You sign a message with your private key and this message can be verified by anyone who has access to your public key. This way, a person who verifies your message is sure that you have access to the private key and it is very likely that you are also associated with the public key. Or maybe you stole the private key, did you? 🤔</p><p>Here are two diagrams show you how it looks like:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzbZPspxBpMda6319lk4BkAoDorG60j5CeqfJpeMmSibkzj4to7uHblFmq9LT2vNWMnbOI_zVx1d2HIYxipvT8F3ip4q_NkLWVo8a5SAlTX87prnPJchu0zyipzGNZ7cRbyMR4kYLu0NA/s544/sign-with-data-key-pair.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="343" data-original-width="544" height="253" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzbZPspxBpMda6319lk4BkAoDorG60j5CeqfJpeMmSibkzj4to7uHblFmq9LT2vNWMnbOI_zVx1d2HIYxipvT8F3ip4q_NkLWVo8a5SAlTX87prnPJchu0zyipzGNZ7cRbyMR4kYLu0NA/w400-h253/sign-with-data-key-pair.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgJbZX93RpYHuEkjURg17g6_Jgkc5p1exFzOThWTfotk_apACt-NZBZxJ0_A85INz6ePSKPhGbsSJVXJCMS_Tdi7aBBkPfoI77gbLdesNy3StD8p_V6b2WVRIlBR4r6HGuXdhS3F8Gv84/s562/verify-with-data-key-pair.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="334" data-original-width="562" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgJbZX93RpYHuEkjURg17g6_Jgkc5p1exFzOThWTfotk_apACt-NZBZxJ0_A85INz6ePSKPhGbsSJVXJCMS_Tdi7aBBkPfoI77gbLdesNy3StD8p_V6b2WVRIlBR4r6HGuXdhS3F8Gv84/w400-h238/verify-with-data-key-pair.png" width="400" /></a></div><p style="text-align: left;">You generate a signature for a message using your private key and put them (the message, the signature and your public key) somewhere on the Internet. Then, anyone with access to your public key can verify that it was signed by your private key and it has not changed since it was signed.</p><p></p><h3 style="text-align: left;">Rotating customer master keys</h3><p style="text-align: left;">And the last section! 👏</p><p style="text-align: left;">You can rotate your customer master keys every year.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1pJuXmeeRQG_fgIxIFMtlwl0k656atmqhKNSCibwCnw0-_LGN6ML9rZsmLYi7QtMQAK7OLqhV3FtZZW6IQIQygfck1sn_0P3zQpbN9JZkhbnVTA2dQT1tDYxk6jjTYkOHxBhFt6tghj8/s988/key-rotation-auto.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="221" data-original-width="988" height="144" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1pJuXmeeRQG_fgIxIFMtlwl0k656atmqhKNSCibwCnw0-_LGN6ML9rZsmLYi7QtMQAK7OLqhV3FtZZW6IQIQygfck1sn_0P3zQpbN9JZkhbnVTA2dQT1tDYxk6jjTYkOHxBhFt6tghj8/w640-h144/key-rotation-auto.png" width="640" /></a></div><p style="text-align: left;">You cannot do it manually. You need to enable it in AWS KMS and AWS will schedule a yearly rotation for this CMK. After that, every year AWS KMS will generate a new <i>backing key</i> (cryptographic material) for you. You do not have to change anything. All your old <i>backing keys</i> are still there (AWS KMS does not delete any rotated key material until you delete the CMK), so you can still decrypt the data key which was encrypted five years ago for instance. But, any new data key will be encrypted by this new <i>backing key</i>.</p><p style="text-align: left;">Here is a video, if you don't want to read:</p><p style="text-align: left;"></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="354" src="https://www.youtube.com/embed/felwHikHh8U" width="587" youtube-src-id="felwHikHh8U"></iframe></div><br /> <p></p><p></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-13868154911877196512021-01-16T16:06:00.009+01:002021-02-02T16:49:56.400+01:00Managing Secrets in GitLab / Git<div style="text-align: left;"><p style="text-align: justify;">Let's say that you have to log in via ssh into an instance, and you work with GitLab, so you want to keep the private key in GitLab somewhere. Is it secure? Let's see!</p><h2 style="text-align: left;">Custom environment variables</h2><p style="text-align: justify;">You can use custom environment variables. <a href="https://docs.gitlab.com/ee/ci/variables/README.html#custom-environment-variables" target="_blank">Here</a> you can read more about them (Developers cannot change them, only Maintainers and Owners can). There are two types of variables:</p><ul style="text-align: justify;"><li>Variable (the runner creates an environment variable that uses the key for the name and the value for the value)<br /></li><li>File (the runner creates an environment variable that uses the key for the
name. For the value, the runner writes the variable value to a temporary
file and uses this path)<br /></li></ul><p style="text-align: justify;">It seems that we can use <i>File</i> type for our purpose. We can set up it via <a href="https://docs.gitlab.com/ee/api/project_level_variables.html" target="_blank">API </a>or <a href="https://docs.gitlab.com/ee/ci/variables/README.html#create-a-custom-variable-in-the-ui" target="_blank">UI</a>. So, let's do that! Go to project's <b>Settings > CI/CD</b>. There will be <b>Variables</b> section (btw, you can specify variables also per <i>group</i> and even for <i>all projects</i> (in admin panel)). Click <i>Add Variable</i> button and add a variable:</p><ul style="text-align: justify;"><li><b>Key:</b> PRIV_KEY</li><li><b>Value:</b> content of our private key</li><li><b>Type:</b> File<br /></li></ul><p style="text-align: justify;">There are also three other choices: <i>Environment scope</i>, <i>Protect variable</i> and <i>Mask variable</i>. With <i>Environment scope</i> you can decide that this variable will be available only on a specific environment (by default each environment has access to this variable). With <i>Protect variable</i> you can export the variable to pipelines running only on protected branches or tags. With <i>Mask variable</i> our variable will be masked in job logs (the variable is hidden in job logs). But, not all variables can be masked, and sadly our private key cannot be. <a href="https://docs.gitlab.com/ee/ci/variables/README.html#masked-variable-requirements" target="_blank">Here</a> is more info about it. And, even then... masking your variables is not so secure as you think.</p><p style="text-align: justify;">Here is an example. I have <span style="background-color: #eeeeee;"><i>$TEST_SECRET</i></span> variable, and the content is a secret for you! If you try to "get into" this variable, GitLab will prevent you for this, and it is great: <br /></p><p style="text-align: center;"><img height="213" src="data:image/png;base64,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RrYQyEVAd5bA9PjE3IcBmoiI0uUgQJstX1eHmupbsODa6Sge7/y0aLYDtCDkIu/XowyXr44Gky9DyHt1CnKD5ldWdCpvzSj4NxXB/2WZIaj5dpmvmaxun/5Sz/6Wcni3KKshuHVlB4bQUCjA91wh/DGXYvbtDsH7XjFcJpflVnnuGRnXFr5dIXg3FsH969GmE7Xy/mOsMuFMPZUeufy3O1IGkdzACNDqMdJPnvNuKYVrzRhDiUdGA/TeUNK1sx33A9263477QRp9RxCc10ALggDXbA/8GyYb9i022OovvpO3rSxSG67xPma81LdgsqSdndchIiKyK3OTCAc5NSR5t4dMTwMTZULiVTiIiIhoMGCAjtAvYZfJtW6J9BigiYiIBj8G6Ah1pr62CgZR5jFAExERDX4M0ET9iAGaiIho8GOAJupHDNBERESDHwM0EREREZEDDNBERERERA4wQBMREREROcAATURERETkAAM0EREREZEDDNBERERERA4wQBMREREROcAATURERETkAAM0EREREZEDDNBERERERA4wQBMREREROcAATURERETkAAM0EREREZEDDNBERERERA4wQBMREREROcAATURERETkAAM0EREREZEDDNBERERERA4wQBMREREROcAATURERETkAAN0hPeuz9Aly4j962y6Fy5X9rdvqHAJL6DVpJ3lrp24y+XJ+vYRERERJeMsQLtHYXLFbCxYVI3aegmSJEGqq8HSm+ZiWtEYuC0e5yqbq9zX0lLMKHRntSEYoPsHAzQRERENdo4CtBaE61BTXY3q6uVakJbuwC0zJ5mG6Ojj6mpQXV1t4kZMKxgYAVpubUzcBsK6aADsOfQ8RJPn6byq3N699zHT5xj3zAH0RJ5DPrMPa1x5CV6zANLLn+NwWxcu9kYeE+7F+Y6T2LPpYcwVXYb7r2pqjwT/++KC/+jVW9ERlgFcxNGGBbp9egFHr8aHWkPA1bWLS1hnev9wz3l0d5zEntf/FcGY7Ura7gzQRERENEg4G4EuqsCs0okQvbr/uUeh+NpFqJckSNItmDY2PjhFA3TV1KzvsBW7ATpvxSfoUMNvz2H8MeA13P7g7tO4ahI6NSJeOXoJMk6jq+sqZJzDnocnmb+euAof//18wmDb2XSv4TFWAdolPoR9Z64CuIL2pgcMwT9TAdoQps8dxV+rCuy3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" width="400" /> </p><p style="text-align: justify;">As you can see, you cannot use <i><span style="background-color: #eeeeee;">echo</span> </i>or <i><span style="background-color: #eeeeee;">cat</span> </i>command and get the content of this variable. I even tried to encode the content into <span style="background-color: #eeeeee;"><i>base64</i></span>, save this to a file, and then try to read the file; still without a success 🤔. But, we are able to echo the variable and pipe into <span style="background-color: #eeeeee;"><i>base64</i></span> program. And if you copy the output (<span style="background-color: #eeeeee;"><i>c3VwZXItc2VjcmV0LWl0LXNob3VsZC1iZS1pbnZpc2libGUK</i></span>) to your local machine and type <span style="background-color: #eeeeee;"><i>echo "c3VwZXItc2VjcmV0LWl0LXNob3VsZC1iZS1pbnZpc2libGUK" | base64 -d</i></span>, you get: <i><span style="background-color: #eeeeee;">super-secret-it-should-be-invisible</span></i>. It was supposed to be super secret!</p><p style="text-align: justify;">I do not know how it works under the hood, but it seems that GitLab try to mask the content everywhere in the log file, so if you just <i style="background-color: #eeeeee;">echo</i> the variable, you will not see the content, but when you also encode it into <i style="background-color: #eeeeee;">base64</i> for example, GitLab does not know anything about this new string (I mean, it is different than the masked content, right?), so it cannot be masked.</p><p style="text-align: justify;">Anyway. If someone has access to your project and can edit jobs (like me, above), they can do what they want, so giving appropriate permissions is a must. Spawning such variables only on protected branches/tags is also a good idea. This way, you can perform such jobs only on master and staging branches for example and do code review before, and block the suspicious code 👮. You can also use <a href="https://swiety-python.blogspot.com/2020/11/gitlab-rules-keyword.html" target="_blank">rules</a> keyword and give access to run some jobs to only a small subset of people.</p><p style="text-align: justify;">Here is an example, how you can use custom environment variable to keep a private key:<br /></p>
<script src="https://gist.github.com/tomislater/eb688d607850027ab85471735d9d9da9.js"></script>
<p style="text-align: justify;">It is a quite good solution. But, it has a flaw. Your variables are not versioned. If there are more than two maintainers/owners, it might be a problem. Someone can change a variable and you will not be notified about it. Maybe, we can keep our secrets in git repository? Then, we will have versioning by default. Such approach would be useful for storing passwords for example.</p><h2 id="sops" style="text-align: left;">SOPS: Secrets OPerationS</h2><p style="text-align: justify;">We can use <a href="https://github.com/mozilla/sops" target="_blank">sops</a> for this:<br /></p><p style="text-align: justify;"></p><blockquote><b>sops</b> is an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault and PGP.</blockquote><p></p><p style="text-align: justify;">Using <b>sops</b>, we can keep our "secrets" secret 🤫. We are going to use AWS KMS for encrypting and decrypting, but please, keep in mind that it is only for demo purpose. If you want to use it to keep some secret data, it makes sense to create more than one master key. According to sops documentation:</p><p style="text-align: justify;"></p><blockquote>Multiple master keys allow for sharing encrypted files without sharing master keys, and provide a disaster recovery solution. The recommended way to use sops is to have two KMS master keys in different regions and one PGP public key with the private key stored offline. If, by any chance, both KMS master keys are lost, you can always recover the encrypted data using the PGP private key.</blockquote><p></p><p style="text-align: justify;">By default, <i>sops</i> encrypts the data key with each of the master keys. So, the file can be decrypted by each master key. You need only one master key for decrypting.</p><p>We need to create a symmetric key in <a href="https://aws.amazon.com/kms/" target="_blank">AWS KMS</a>. Then we have to set up <i>sops</i> by creating a file called <i style="background-color: #eeeeee;">.sops.yaml</i> and putting the info about our ARN(s) (Amazon Resource Name(s)). Here is an example:<br /></p>
<script src="https://gist.github.com/tomislater/5a2b60022b44bb7f4c5a151cf40c76aa.js"></script>
<p style="text-align: justify;">After that we have to create a file where we want to keep our secrets. Let's say that we want to keep there our private key and some passwords. And, we are going to keep all of this in <span style="background-color: #eeeeee;"><i>YAML</i></span> file. So, type <span style="background-color: #eeeeee;"><i>sops secrets.yaml</i></span>, you will see an example in your default editor, delete it and put there this (no, it is not my private key, do not worry):</p>
<script src="https://gist.github.com/tomislater/17a82b181d59a83aa9c613d6642476b4.js"></script>
<p style="text-align: justify;">When you close the editor, <i>sops</i> will save the file with encoded values:</p>
<script src="https://gist.github.com/tomislater/b36db268274881757d17b189c40ed933.js"></script>
<p style="text-align: justify;">Such file without master key is useless for others, so we can keep this guy in git repository without worry. We can also <a href="https://github.com/mozilla/sops#key-rotation" target="_blank">renew</a> the data key on regular basis. You can also use <a href="https://github.com/mozilla/sops#key-groups" target="_blank">key groups</a>. This way you can tell <i>sops</i> that some data must be decrypted by providing two (or more; there also might be some thresholds like, at least 3 of 5 keys for instance) keys (AWS KMS and PGP for example).</p><p style="text-align: justify;">If someone wants to get the content of our file without master key, they will get error like:</p><p style="text-align: justify;"></p><div class="separator" style="clear: both; text-align: center;"><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhod2cz4iBORqkdbONLayQ_zLdbVxtgC3Og9sDVbFpI1vFweaTOK5BQBsL3KelIprZtpBb68Tkk_yqrWH_P3BzK2Y_edunQAo0lq2z0TeHYdOxN2UfmbGSe9y8r7CwNgCNsZlXQYnt1FLk/s1263/sops+without+master+key.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="466" data-original-width="1263" height="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhod2cz4iBORqkdbONLayQ_zLdbVxtgC3Og9sDVbFpI1vFweaTOK5BQBsL3KelIprZtpBb68Tkk_yqrWH_P3BzK2Y_edunQAo0lq2z0TeHYdOxN2UfmbGSe9y8r7CwNgCNsZlXQYnt1FLk/w640-h237/sops+without+master+key.png" width="640" /></a></p></div><p></p><p style="text-align: justify;">Now, we want to get secrets from our file in a job. If we use <a href="https://aws.amazon.com/kms/" target="_blank">AWS KMS</a>, we need to provide somehow credentials for it (<i>AWS Access Key ID</i>, <i>AWS Secret Access Key</i>). In most cases we can keep them as custom environment variables (like in the first example). But, if you think that someone evil can do very bad things, you can provide these environment variables during spawning pipeline or job:</p><p style="text-align: justify;"></p><div class="separator" style="clear: both; text-align: center;"><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAeD4SPGZB0TyPV5G-W5Cn49zvZqZ3vobzagWLUrPN3LAL10Vy9-owSA89WGtim0ZTwFDn9PRYBUfb1CLU6027IRi1VHTerCm66BtgsEtODgg1cjIylRNQdf_aGIgnHUJ5rGCXjEVagWY/s1941/provide+creds.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="753" data-original-width="1941" height="248" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAeD4SPGZB0TyPV5G-W5Cn49zvZqZ3vobzagWLUrPN3LAL10Vy9-owSA89WGtim0ZTwFDn9PRYBUfb1CLU6027IRi1VHTerCm66BtgsEtODgg1cjIylRNQdf_aGIgnHUJ5rGCXjEVagWY/w640-h248/provide+creds.png" width="640" /></a></p></div><p></p><p style="text-align: justify;">You should also remember about enabling <i>Protect variable</i> option. It is very important. Without this, everyone who can create a new branch, will get an access to these environment variables; then they can modify a job, and <span style="background-color: #eeeeee;"><i>echo</i></span> some passwords for example. We have seen that we can mask our variable, but you can still unmask it via <span style="background-color: #eeeeee;"><i>base64</i></span> for instance (if someone does such things in your team, I think you have other problems than that 😅). <br /></p><p style="text-align: justify;">How to get values from this file then? You can just type <span style="background-color: #eeeeee;"><i>sops -d secrets.yaml</i></span>, but this will give you all the data. What if we want to get only a password for <i>redis</i>? You can do that easily by <span style="background-color: #eeeeee;"><i>sops -d --extract '["dev"]["redis"]' secrets.yaml</i></span>:</p><p style="text-align: justify;"></p><div class="separator" style="clear: both; text-align: center;"><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWfhaFiFdVJvauMgaw87V91Eq_DSjCOYfoxJifeBiAnluOC_ExEW2VBqrixk_Win3AP10fV09rtk-QVSAsTL1BF4Txbh0ZY0ziOvMMRhH4itqPGHHqKuEdUS31foU3sjRahYZYgsvLLvM/s738/sops+redis+output.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="88" data-original-width="738" height="76" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWfhaFiFdVJvauMgaw87V91Eq_DSjCOYfoxJifeBiAnluOC_ExEW2VBqrixk_Win3AP10fV09rtk-QVSAsTL1BF4Txbh0ZY0ziOvMMRhH4itqPGHHqKuEdUS31foU3sjRahYZYgsvLLvM/w640-h76/sops+redis+output.png" width="640" /></a></p></div><p style="text-align: left;">So, how would look like our <i>gitlab </i>job if we wanted to use <i>sops</i> for getting a private key and logging into our instance? It might be something like that:</p>
<h2 style="text-align: left;"><script src="https://gist.github.com/tomislater/ba51e0c5ca35d284863b0a1f1ac0f471.js"></script>Can I use it with Helm?</h2><p style="text-align: left;">Yes! There is a plugin for <a href="https://helm.sh/" target="_blank">Helm</a> called <a href="https://github.com/jkroepke/helm-secrets" target="_blank">helm-secrets</a>. Instead of deploying your service by <span style="background-color: #eeeeee;"><i>helm upgrade ...</i></span>, you must use <span style="background-color: #eeeeee;"><i>helm secrets upgrade -f secrets.yaml ...</i></span>. Let's look for an example!</p><p style="text-align: left;">Let's say that we have two environments: <span style="background-color: #eeeeee;"><i>dev</i></span> and <i><span style="background-color: #eeeeee;">prod</span></i>. On <span style="background-color: #eeeeee;"><i>dev</i></span> environment we are fine with storing passwords in repository, but on <span style="background-color: #eeeeee;"><i>prod</i></span> we do not want to do that. We want to deploy <a href="https://github.com/bitnami/charts/tree/master/bitnami/grafana" target="_blank">grafana</a> both on <span style="background-color: #eeeeee;"><i>dev</i></span> and <span style="background-color: #eeeeee;"><i>prod</i></span> environments. Here is our root catalogue:</p><p style="text-align: left;"></p><div class="separator" style="clear: both; text-align: center;"><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbioGIbqdNub0j1gS5OlqoPQzBuhYZPUJAvQRqUtTaBtEFMb15fd828WGO-Bcp6w11arTqn-tVHXnpK_bdvcm84k_sgQSDnb2c1qSilrCQ-XGltwboVO8dSDVOCI4v_4Y3F9hUjzMX9ws/s424/root+cat.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="424" data-original-width="295" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbioGIbqdNub0j1gS5OlqoPQzBuhYZPUJAvQRqUtTaBtEFMb15fd828WGO-Bcp6w11arTqn-tVHXnpK_bdvcm84k_sgQSDnb2c1qSilrCQ-XGltwboVO8dSDVOCI4v_4Y3F9hUjzMX9ws/w446-h640/root+cat.png" width="446" /></a></p></div><p></p><p style="text-align: left;">And here is a code:</p>
<script src="https://gist.github.com/tomislater/a66dd3b4f4acddebd431ff48cec89044.js"></script>
<p style="text-align: left;">How it works then? We have two jobs: <span style="background-color: #eeeeee;"><i>dev</i></span> and <span style="background-color: #eeeeee;"><i>prod</i></span>. We install <i>sops</i> and <i>helm-secrets</i> plugin, add <i>bitnami</i> repository to <i>helm</i> and check if there is a <span style="background-color: #eeeeee;"><i>secrets.yaml</i></span> file. If it exists, then we want to use it, so we must change the command from <span style="background-color: #eeeeee;"><i>helm upgrade</i></span> to <span style="background-color: #eeeeee;"><i>helm secrets upgrade</i></span> and use both <span style="background-color: #eeeeee;"><i>values.yaml</i></span> and <span style="background-color: #eeeeee;"><i>secrets.yaml</i></span> files. Here is a content of these files:</p>
<script src="https://gist.github.com/tomislater/09a6079c6ea557ef72e4824c3e9f14ca.js"></script>
<script src="https://gist.github.com/tomislater/9cb5adea1af27f7860ced7eaf37f8fe6.js"></script>
<p style="text-align: left;"></p><p style="text-align: left;"><a href="https://gitlab.com/examples16/secret" target="_blank">Here</a> is a link to the repository, so you can see the whole picture how it might work. I hope it was useful! Of course, there are many other tools for storing secret data (<a href="https://www.vaultproject.io/" target="_blank">Vault</a> by HashiCorp for instance), but I think that <i>sops</i> and <i>custom environment variables</i> are enough in most cases.</p><p style="text-align: left;">Here is a video if you don't want to read:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="344" src="https://www.youtube.com/embed/e085yXUjrjE" width="608" youtube-src-id="e085yXUjrjE"></iframe></div><br /><p style="text-align: left;"><br /></p></div>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-37270950116655273682021-01-08T23:43:00.003+01:002021-01-08T23:55:21.197+01:00Terraform - import aws_s3_bucket does not store important attributes like acl<p style="text-align: left;">Recently, I had to import some AWS resources to terraform, and most things went smoothly, but some did not. More specifically, I have encountered <a href="https://github.com/hashicorp/terraform-provider-aws/issues/6193" target="_blank">this</a> problem. And <a href="https://github.com/hashicorp/terraform-provider-aws/issues/6193#issuecomment-756708027" target="_blank">here</a> is my reply how to deal with it now. In this post, I am going to be more elaborate about this issue.</p><p style="text-align: left;">So, what exactly I have run into? Here is the code:</p>
<script src="https://gist.github.com/tomislater/9f3320677991bed027546e1a607e6efd.js"></script>
<p style="text-align: left;">Such bucket existed and I wanted to import this guy to terraform (the bucket was public). So, I typed <i style="background-color: #eeeeee;">terraform import 'aws_s3_bucket.my-bucket' 'my-bucket'</i> and pressed enter:</p>
<script src="https://gist.github.com/tomislater/1abc01726877337830f52be795697e51.js"></script>
<p style="text-align: left;">Wait, what? I understand the <i style="background-color: #eeeeee;">force_destroy</i> argument (it is <i style="background-color: #eeeeee;">false</i> by default), because I had not specified it, but <i style="background-color: #eeeeee;">acl</i>? I have two <i style="background-color: #eeeeee;">grant</i> blocks... and according to <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#acl" target="_blank">the documentation</a>, <i style="background-color: #eeeeee;">acl</i> conflicts with <i style="background-color: #eeeeee;">grant</i>. So, how is it even possible? 🤔</p><p style="text-align: left;">It was tempting to run <i style="background-color: #eeeeee;">terraform apply</i> command... so let's do that!</p>
<script src="https://gist.github.com/tomislater/b7be01581a622c1745e8c2229b0b09d0.js"></script>
<p style="text-align: left;">And what happened? Terraform (or should I say aws provider?) ignored these <i style="background-color: #eeeeee;">grant</i> blocks and removed some ACL (Access control list) records from my bucket (the bucket was converted to the private one after this action). Why the <i style="background-color: #eeeeee;">grant</i> blocks were ignored? They were there the whole time, they are still there.</p><p>Hmm, what if I run <i style="background-color: #eeeeee;">terraform apply</i> again? 😅</p>
<script src="https://gist.github.com/tomislater/7c7a3c6cf0cf2b448cf67274e7c71771.js"></script>
<p>ACL records (<i style="background-color: #eeeeee;">grant</i> blocks) had been removed (from terrafom state (and from my bucket ☹️), not from my code) in the first apply and now in the second one terraform wants to add them. I cannot accept such behaviour on production; I don't want to make my bucket private for some minutes/seconds on prod env. It was funny on dev environment, but not on production.</p><p>So, it is time for lifecycle Meta-Argument! ⛑ And specifically <a href="https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes" target="_blank">ignore_changes</a> argument. This way we can define which arguments should be ignored during an update procedure. After changes, my code looked like this:</p>
<script src="https://gist.github.com/tomislater/48634c03d84b9f6fac9b182f3e8a50b4.js"></script>
<p>I had to add both <i style="background-color: #eeeeee;">acl</i> and <i style="background-color: #eeeeee;">force_destroy</i> arguments to the <i style="background-color: #eeeeee;">ignore_changes</i> list. Adding only <span style="background-color: #eeeeee;"><i>acl</i></span> did not resolve the problem. Terraform showed that only this argument would be added, but ACL records were removed also 🤷.</p><p>Using this meta-argument I can import production buckets fearlessly. Terraform does not want to change (remove if I want to be more specific) ACL records this way. It works, but it is quite hacky and rather should be resolved on aws provider side, methinks. I have not fixed any issue in terraform repo yet, so maybe it is time to learn something new, help others and fix this bug.</p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-61722477491877678062021-01-07T23:03:00.005+01:002021-01-07T23:48:00.040+01:00GitLab - Spawn a job with any command you want<p><b>The problem</b>: you have many scripts (let's say that they are written in python and you just want to run them typing <i style="background-color: #eeeeee;">python your-script.py</i>) and sometimes you want to run some of them, sometimes only one, etc. There is no pattern. Additionally you want to <a href="https://swiety-python.blogspot.com/2020/11/gitlab-trigger-keyword.html" target="_blank">trigger</a> these scripts via GitLab API.</p><p>How can you do this? The first idea: let's create a job for each of them! But... then what? You want to run only a small subset of them and each time this subset might be different ☹️. You might add variables, check them and run only these jobs when IF evaluates to true, like:</p>
<script src="https://gist.github.com/tomislater/adeab2a39c52168931c3c22d05bf5cd5.js"></script>
<p>But, I think that you see the problem of this approach.</p><p>What about creating a common job without any command? It will be your job to provide a command for script section (for example <i style="background-color: #eeeeee;">python run-something-and-upload-to-s3.py</i>). This way we will have only one job in GitLab and during triggering you must provide a command.</p><p>The code:</p>
<script src="https://gist.github.com/tomislater/a5967ac93f23501784554eb94a00a8f3.js"></script>
<p>8 lines. Woah! We used <a href="https://swiety-python.blogspot.com/2020/11/gitlab-rules-keyword.html" target="_blank">rules</a> keyword, because we want to spawn only this job when it is triggered by GitLab API. You can read more about triggering <a href="https://docs.gitlab.com/ee/ci/triggers/" target="_blank">here</a>. In the <i>script</i> section there is variable: <i style="background-color: #eeeeee;">$COMMAND_TO_RUN</i>. It will be our command.</p><p>Okay, so now, how can I trigger any command? You can do it using curl for example:</p>
<script src="https://gist.github.com/tomislater/2315d03bc586cec2f43749e59ef46a23.js"></script>
<p>What did happen here? We triggered a pipeline and additionally we passed our command <i style="background-color: #eeeeee;">$COMMAND_TO_RUN</i>. <a href="https://gitlab.com/examples16/run-command" target="_blank">Here</a> is the project, so you can see the whole picture.</p><p>If you want to run other scripts then you need to change the value of <i style="background-color: #eeeeee;">$COMMAND_TO_RUN</i> in a request, and that's it. You are able to run another script.</p><p><a href="https://gitlab.com/examples16/run-command/-/jobs/950357450" target="_blank">Here</a> is the output of another script for example.</p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-76945630861757463132021-01-05T22:46:00.001+01:002021-01-07T23:47:51.697+01:00Terraform - Create two buckets in two different regions using meta-argument<p style="text-align: left;">Let's say that you provision your AWS resources by <a href="https://www.terraform.io" target="_blank">Terraform</a>, and mostly you keep everything in Oregon region, but you have some S3 buckets in another region (California for example). How you can deal with that? You can specify a meta-argument <a href="https://www.terraform.io/docs/configuration/meta-arguments/resource-provider.html" target="_blank">provider</a> for a specific resource!</p><p style="text-align: left;">Firstly, you must define two providers (default one for Oregon, and another one for California):</p>
<script src="https://gist.github.com/tomislater/b5356d0b5c03c90c2b1325ae38472141.js"></script>
<p style="text-align: left;">The alias is very important, we are going to use it in a minute 🏃♂️</p><p style="text-align: left;">Now, let's create two buckets, one in Oregon and another one in California:</p>
<script src="https://gist.github.com/tomislater/1b8128c37c9c0061d9eaef20ace8aa4f.js"></script>
<p style="text-align: left;">For bucket in Oregon, we do not have to specify a provider because:</p><blockquote><p style="text-align: left;">By default, Terraform interprets the initial word in the resource type name (separated by underscores) as the local name of a provider, and uses that provider's default configuration.</p></blockquote><p style="text-align: left;">In our example, it is "aws". For bucket in California we must select another provider. And for that we use alias "california" (<i>aws.california</i>).</p><p style="text-align: left;">That's it, folks.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXLHObhsCBETHseRBJPC3cRJzDi2Qzj9Oy5q5BdUpb4XQgmbYc9LHRgUXXQEnFLu7_dAehtF0HoljarOcxApdN02YYpivxVYdrlbFAygcsYxdkyUghMk1WfeaN4nove5q7ix1Trf7wD7I/s800/honest-work.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="800" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXLHObhsCBETHseRBJPC3cRJzDi2Qzj9Oy5q5BdUpb4XQgmbYc9LHRgUXXQEnFLu7_dAehtF0HoljarOcxApdN02YYpivxVYdrlbFAygcsYxdkyUghMk1WfeaN4nove5q7ix1Trf7wD7I/w640-h360/honest-work.jpg" width="640" /></a></div><br /><p style="text-align: left;"><br /></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-7072733932087960732021-01-04T13:17:00.001+01:002021-01-07T23:47:42.203+01:00GitLab - Run the same job on multiple images<p style="text-align: left;">Sometimes you want to test your code on different python versions. Using Travis CI it is very easy, but can we do that or something similar in GitLab? Let's see!</p><p style="text-align: left;"><b>The problem</b>: there are some tests written in python and we want to test it on different python versions.</p><p style="text-align: left;">We can easily use <a href="https://swiety-python.blogspot.com/2020/11/gitlab-extends-keyword.html" target="_blank">extends</a> keyword, define some common logic in <i style="background-color: #eeeeee;">.test</i> job and then inherit it in other jobs:</p>
<script src="https://gist.github.com/tomislater/8c9764c21ac116489055d0e0fa383b2f.js"></script>
<p style="text-align: left;">But... for each new version we have to define a new job. Maybe, we can do better?</p><p style="text-align: left;">GitLab has introduced a <a href="https://docs.gitlab.com/ee/ci/yaml/#parallel" target="_blank">parallel</a> keyword. Additionally, there is a <a href="https://docs.gitlab.com/ee/ci/yaml/#parallel-matrix-jobs" target="_blank">matrix</a> keyword; using this guy you</p><blockquote><div style="text-align: left;">can run a job multiple times in parallel in a single pipeline, but with different variable values for each instance of the job.</div></blockquote><p style="text-align: left;">But, you cannot change <i style="background-color: #eeeeee;">image</i> keyword for example 😞. Maybe there is another way?</p><p style="text-align: left;">Have you ever heard about dind (Docker-in-Docker)? If not, <a href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-the-docker-executor-with-the-docker-image-docker-in-docker" target="_blank">here</a> is some info about it:</p><p style="text-align: left;"></p><blockquote>Another way to configure GitLab Runner for docker support is to register a runner with the Docker executor and use the Docker image to run your job scripts. This configuration is referred to as “Docker-in-Docker.”</blockquote><p></p><p style="text-align: left;">So... we can spawn a service (using a dind image) and then run <i style="background-color: #eeeeee;">docker run <image></i> commands in our jobs. Then, using parallel and matrix keywords we can construct a... matrix!</p><p style="text-align: left;">Here is an example:</p>
<script src="https://gist.github.com/tomislater/f0462aa83b69dc2b4a1c66cf8446d55e.js"></script>
<p style="text-align: left;">This way, in a pipeline there will be three jobs and each of them will run different python versions, but the command will be the same.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5HiKJ86RegvtFk_CVhoxADq6LmtDXzW6Jz4tNficdcP4RHBy1HHALiJ-ppUB03rDEt5PBGFQaedDME1edXjvRKt5WT1b7eBn7AJBR3-DHPhV1j2VCtzr97Y77ZwmI3zW_X8oIiKSagpM/s2406/Screenshot+2021-01-04+at+12.48.56.png" style="margin-left: 1em; margin-right: 1em;"><img alt="test jobs" border="0" data-original-height="762" data-original-width="2406" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5HiKJ86RegvtFk_CVhoxADq6LmtDXzW6Jz4tNficdcP4RHBy1HHALiJ-ppUB03rDEt5PBGFQaedDME1edXjvRKt5WT1b7eBn7AJBR3-DHPhV1j2VCtzr97Y77ZwmI3zW_X8oIiKSagpM/w640-h202/Screenshot+2021-01-04+at+12.48.56.png" width="640" /></a></div><br /><p style="text-align: left;">Maybe in the near future GitLab will add a support (somehow 🤷) and it will be possible to change <i style="background-color: #eeeeee;">image</i> keyword for example, but now we have to deal with it this way using dind.</p><p style="text-align: left;"><a href="https://gitlab.com/examples16/multiple-images" target="_blank">Here</a> you will find a repository with an example.</p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-15902994510331825642020-12-11T21:16:00.002+01:002020-12-13T18:03:36.199+01:00GitLab - terraform plan and apply<p style="text-align: left;">How do you apply changes in <a href="https://www.terraform.io" target="_blank">terraform</a>? In most cases you run <i style="background-color: #eeeeee;">terraform plan</i> and then <i style="background-color: #eeeeee;">terraform apply</i> and type <i style="background-color: #eeeeee;">yes</i>. This approach works great on your local machine, but how to apply changes (and only the changes you want!) in GitLab job where you do not have access to shell? How to do that, when you cannot approve the output of <i style="background-color: #eeeeee;">apply</i> command?</p><p style="text-align: left;">You can use <i style="background-color: #eeeeee;">terraform apply -auto-approve</i>, but it might be risky... No one likes to destroy something on production without a priori knowledge. So, can we run <i style="background-color: #eeeeee;">terraform plan</i>, check the output and then run <i style="background-color: #eeeeee;">terraform apply</i> in another step? We can, but still it might be risky operation. Why? Because <i><span style="background-color: #eeeeee;">plan</span> </i>and <i style="background-color: #eeeeee;">apply</i> are separated operations! They know nothing about each other. So, <i style="background-color: #eeeeee;">apply</i> can change something which was not showed in <i style="background-color: #eeeeee;">plan</i>.</p><p style="text-align: left;">But... according to <a href="https://www.terraform.io/docs/commands/plan.html" target="_blank">Terraform Documentation</a>:</p><blockquote><p style="text-align: left;">The optional <i style="background-color: #eeeeee;">-out</i> argument can be used to save the generated plan to a file for later execution with terraform apply, which can be useful when running Terraform in automation.</p></blockquote><p>This way we can run <i style="background-color: #eeeeee;">terraform plan</i> with <i style="background-color: #eeeeee;">-out</i> parameter and then use this file in <i style="background-color: #eeeeee;">terraform apply</i> command. Therefore, we know that only changes from <i>plan</i> command will be applied (if there were any, of course).</p><p>So, let's say that we have two environments: <i>dev</i> and <i>prod</i>. We want to run <i style="background-color: #eeeeee;">terraform plan</i> command, and if everything is alright, we want to run <i style="background-color: #eeeeee;">terraform apply</i> and it should apply changes from the plan only. We need to save a file from <i style="background-color: #eeeeee;">plan</i> command and use it later in <i><span style="background-color: #eeeeee;">apply</span>.</i> For this we can use <a href="https://docs.gitlab.com/13.6/ee/ci/yaml/README.html#artifacts" target="_blank">artifacts</a> keyword. We will also use some other keywords. Here is the first part of the <i>.gitlab-ci.yml</i> file (link to the repository you will find at the end of the post):</p>
<script src="https://gist.github.com/tomislater/01620f38705b091ae1f7fcb3491c086a.js"></script>
<p>We keep our terraform logic in <i>dev</i> and <i>prod</i> catalogs. We have two stages: <i>plan</i> and <i>apply.</i><br /></p><p><i> .common</i> job: This is a hidden job which will be used later by <a href="https://swiety-python.blogspot.com/2020/11/gitlab-extends-keyword.html" target="_blank">extends</a> keyword. We provide terraform image (obviously) and entrypoint for this image (by default, entrypoint is set to terraform command, so without changing entrypoint we couldn't run some commands like <i style="background-color: #eeeeee;">cd</i> for example). The job will be manual, and we also have <i>before_script</i> section. We want to go to <i>dev</i> or <i>prod</i> (<i style="background-color: #eeeeee;">$ENV_NAME</i> environment variable) catalogue and run <i style="background-color: #eeeeee;">terraform init</i> command.</p><p> <i>.plan</i> job: Another hidden job which will be used later. Here we inherit from <i>.common</i> job using <a href="https://swiety-python.blogspot.com/2020/11/gitlab-extends-keyword.html" target="_blank">extends</a> keyword, we set <i>stage</i> to <i>plan</i>, and run <i><span style="background-color: #eeeeee;">terraform plan</span> </i>command with param <i><span style="background-color: #eeeeee;">-out</span>.</i> This way we tell <i>terraform</i> to save the plan to a specific file: <i style="background-color: #eeeeee;">$ENV_NAME.plan</i>. This environment variable <i style="background-color: #eeeeee;">$ENV_NAME</i> we will provide in another jobs. Additionally, we set <i>artifacts</i> section. With this we can easily save whatever we want and use it later in other jobs. So, here we want to keep the generated plan.</p><p> <i>.apply</i> job: Here we also inherit from <i>.common</i> job, but <i>stage</i> is set to <i>apply</i>, and the command in <i>script</i> section is <i style="background-color: #eeeeee;">terraform apply -input=false $ENV_NAME.plan</i>. We will use this job and <i>.plan</i> soon...</p><p>So we have a backbone; now, we are going to add the rest of stuff. We have two catalogs: <i>dev</i> and prod. I am going to provide a code only for <i>dev</i> jobs, because the logic for <i>prod</i> is the same. The only difference is the name of jobs and the value of <i style="background-color: #eeeeee;">$ENV_NAME</i> environment variable.</p>
<script src="https://gist.github.com/tomislater/7967365cce0f6ed2f3c46c520b503366.js"></script>
<p>We have three jobs here, but one of them is hidden so in a pipeline you will see only two jobs: <i>dev plan</i> and <i>dev apply</i>.<br /></p><p> <i>.dev</i> job: we only set a variable here.<br /></p><p> <i>dev plan</i> job: in this job we only inherit from the previous ones: <i>.plan</i> and <i>.dev</i>. We get the whole logic from <i>.plan</i> job, and from<i> .dev </i>we get info about <i style="background-color: #eeeeee;">$ENV_NAME</i> environment variable.</p><p> <i>dev apply</i> job: it is similar to <i>dev plan</i> job, but we inherit from <i>.apply </i>job. Additionally, we use<i> <a href="https://docs.gitlab.com/13.6/ee/ci/yaml/README.html#dependencies" target="_blank">dependencies</a></i> keyword, telling GitLab that we want artifacts only from <i>dev plan</i> job.</p><p>The same thing has been done for prod environment. So, the pipeline looks that:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOyLj_ym8bxdrMJcleNM6RDC-gXiW5VkBKhKAw6DohF0o8etzZdDuIPJ5Bx2YNynj_rhoC__zXOiQ8l5oK8zJCWIfzO_Ml5RAKnmQDk90xI00XI0rcU_PL_JDLaOB2MKos9l32Kqlstu4/s982/Screenshot+2020-12-11+at+21.00.13.png" style="margin-left: 1em; margin-right: 1em;"><img alt="The pipeline" border="0" data-original-height="510" data-original-width="982" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOyLj_ym8bxdrMJcleNM6RDC-gXiW5VkBKhKAw6DohF0o8etzZdDuIPJ5Bx2YNynj_rhoC__zXOiQ8l5oK8zJCWIfzO_Ml5RAKnmQDk90xI00XI0rcU_PL_JDLaOB2MKos9l32Kqlstu4/w400-h208/Screenshot+2020-12-11+at+21.00.13.png" width="400" /></a></div><p style="text-align: left;">In <i>dev plan</i> job you will find such output:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQefnZ0ObaNR7hlNPiBiIvceUjSb2xcmHq1ocMJaZs1YaekbzED-5cGjr0mzJ4b_n2YirTIdh76jbT0NpQOi4Ybb9_cIsmWrxRg2VqM4uir2K2J578z4i1_u4SXBm7vcjyUf57aJjki8g/s2048/Screenshot+2020-12-11+at+21.03.31.png" style="margin-left: 1em; margin-right: 1em;"><img alt="dev plan" border="0" data-original-height="1146" data-original-width="2048" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQefnZ0ObaNR7hlNPiBiIvceUjSb2xcmHq1ocMJaZs1YaekbzED-5cGjr0mzJ4b_n2YirTIdh76jbT0NpQOi4Ybb9_cIsmWrxRg2VqM4uir2K2J578z4i1_u4SXBm7vcjyUf57aJjki8g/w400-h224/Screenshot+2020-12-11+at+21.03.31.png" width="400" /></a></div><p style="text-align: left;">As you can see, <i>terraform</i> saved the plan to <i>dev.plan</i> file (in <i>dev</i> catalog) and the artifact has been uploaded. In the next job <i>dev apply</i> you will find this guy:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNc5Dx7Pvio_rPzZgYd7CkIeO4HDk8o5aC8A-0buIPRqhFB7UyMPlQwmnSnV2Msc5__fTsp2rtgRI1-02SSRxYQI5MDIvvLmxNxnkXQ-0l2uIq952NsN_U874HinpJ58kLroJ7T8_q9_w/s2048/Screenshot+2020-12-11+at+21.08.09.png" style="margin-left: 1em; margin-right: 1em;"><img alt="dev apply" border="0" data-original-height="1336" data-original-width="2048" height="261" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNc5Dx7Pvio_rPzZgYd7CkIeO4HDk8o5aC8A-0buIPRqhFB7UyMPlQwmnSnV2Msc5__fTsp2rtgRI1-02SSRxYQI5MDIvvLmxNxnkXQ-0l2uIq952NsN_U874HinpJ58kLroJ7T8_q9_w/w400-h261/Screenshot+2020-12-11+at+21.08.09.png" width="400" /></a></div><p>And here, we used <i>dev.plan</i> file which has been uploaded by<i> dev plan</i> job! That's it!</p><p><a href="https://gitlab.com/examples16/terraform-plan-apply" target="_blank">Here</a> is the link to the repository <i>terraform-plan-apply;</i> you will find the rest of code there.</p><p>And here is a video:</p><p></p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/TYYdDE-KxuQ" width="320" youtube-src-id="TYYdDE-KxuQ"></iframe></div><br /> <br /><p></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-54827514880208957842020-11-23T16:00:00.005+01:002020-12-11T21:21:27.050+01:00GitLab - trigger keyword<p> We are going to talk about the <i><a href="https://docs.gitlab.com/ee/ci/yaml/#trigger" target="_blank">trigger</a> </i>keyword today. With this feature you can define a downstream pipeline trigger. So, you can trigger a pipeline in any project (you must have access to this project of course). There are two types of downstream pipelines:</p><ul style="text-align: left;"><li><a href="https://docs.gitlab.com/ee/ci/multi_project_pipelines.html#creating-multi-project-pipelines-from-gitlab-ciyml" target="_blank">multi-project pipelines</a></li><li><a href="https://docs.gitlab.com/ee/ci/parent_child_pipelines.html" target="_blank">child pipelines</a></li></ul><p style="text-align: left;"> Let's omit child pipelines and have a look at multi-project pipelines. It is very easy to use. You just need to provide a path to the project and that's it. Remember that you only can trigger a pipeline, <b>not a job</b>! You can also provide more information like environment variables.</p><p style="text-align: left;">As always, I am going to provide some examples, so you will understand it better.</p><p style="text-align: left;">The first example: you want to trigger a pipeline in another project after the deployment of your service/s. How to do that? Here we want to trigger this pipeline (project <i>my/run-tests</i>). Let's say that we want to run some tests after each deployment:</p>
<script src="https://gist.github.com/tomislater/1941ef6d78f84352853118b685cf738b.js"></script>
<p style="text-align: left;">We need to add the job which will trigger this pipeline in <i>my/run-tests</i> project. So, we have to add a job in our service and this job will trigger a pipeline on let's say <i>master</i> branch. Here is the example:</p>
<script src="https://gist.github.com/tomislater/cb099f052eed8f1642e0ce1f69045049.js"></script>
<p style="text-align: left;">You can see that the job which triggers a pipeline does not have a <i>script</i> section and it is okay. It also does not have <i>after_script</i> or <i>before_script</i> sections, because it would make no sense. The purpose of this job is to just trigger a pipeline, nothing else. Okay, so what will happen then? When the <i>trigger</i> job starts, it will trigger a pipeline in your project <i>my/run-tests </i>(where we have two jobs: <i>linter</i> and <i>test</i>). And that's it. The last job will just trigger a pipeline in another project.</p><p style="text-align: left;">It is important that by default, the trigger job completes with the success status as soon as the downstream pipeline is created. In most cases it is fine, but sometimes you want to depend on the triggered pipeline. We will touch this topic later.</p><p style="text-align: left;">Let's say that we have many microservices and we want to be able to deploy all of them (from a specific branch) in the same time. We only want to deploy them, but there are many other jobs in their pipelines like linters, tests, building an image, etc. Can we just trigger a specific job? No, we can only trigger a pipeline, but! With some Yaml and GitLab magic, we can create a pipeline with only a specific job!</p><p style="text-align: left;">Firstly, let's look at the pipeline of a microservice:</p>
<script src="https://gist.github.com/tomislater/af853abb87c9f70b5811dc6d78e30213.js"></script>
<p style="text-align: left;">There are only three jobs: build an image, run tests and deploy the image. If you did not change anything, the trigger job would run the whole pipeline. But, we just want to spawn a deploy job, nothing else. How can we do that? We can use the <i><a href="https://swiety-python.blogspot.com/2020/11/gitlab-rules-keyword.html" target="_blank">rules</a></i> keyword and disable some jobs only when a pipeline is triggered:</p>
<script src="https://gist.github.com/tomislater/a32c07851aaf173d43e1d313d28068d8.js"></script>
<p style="text-align: left;">This way, when a pipeline is triggered, you will see only the deploy job in the pipeline. You will find more info about <i>$CI_PIPELINE_SOURCE</i> environment variable <a href="https://docs.gitlab.com/ee/ci/triggers/README.html#authentication-tokens" target="_blank">here</a>. And, here is the example of the job which triggers a pipeline in a microservice:</p>
<script src="https://gist.github.com/tomislater/ebeacf647771e6cd372bb11e827ab08c.js"></script>
<p>Have you spotted the <i>strategy</i> keyword? Great! According to the GitLab documentation: </p><blockquote><p>To force the trigger job to wait for the downstream (multi-project or child) pipeline to complete, use <i>strategy: depend</i>. This setting makes the trigger job wait with a “running” status until the triggered pipeline completes. At that point, the trigger job completes and displays the same status as the downstream job.</p></blockquote><p>So, in your case it will tell us the truth about the deployment status! Here is the output of this pipeline:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUkQpDnaamAVn4hmP_u_t0egkK0q6_JWcga2B60GiuEmjYLj3nKywP-40_Fo954FWf3Ul0LKm8zSUpN080MiJdmTsIIWfrO0U0z03jLagINDfxz4D-VN7U01BmxnCYnOvReG6ukLsKxlw/s1784/Screenshot+2020-11-23+at+15.40.30.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="610" data-original-width="1784" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUkQpDnaamAVn4hmP_u_t0egkK0q6_JWcga2B60GiuEmjYLj3nKywP-40_Fo954FWf3Ul0LKm8zSUpN080MiJdmTsIIWfrO0U0z03jLagINDfxz4D-VN7U01BmxnCYnOvReG6ukLsKxlw/s320/Screenshot+2020-11-23+at+15.40.30.png" width="320" /></a></div><p style="text-align: left;">As you can see our job in project A triggered a pipeline in project B, and additionally, because we changed properly the logic of the pipeline of the project B, the pipeline spawned only the deploy job.</p><p style="text-align: left;">I made these repositories public and you can find deploy-microservices project <a href="https://gitlab.com/tomislater/deploy-microservices" target="_blank">here</a> and microservice-a project <a href="https://gitlab.com/tomislater/microservice-a" target="_blank">here</a>. You can look at <i>.gitlab-ci.yml</i> files there. Have fun!</p><p style="text-align: left;">I have also created a video about this keyword:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/54Ku0WiiixA" width="320" youtube-src-id="54Ku0WiiixA"></iframe></div><br /><p style="text-align: left;"><br /></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-16170155183748924482020-11-20T14:19:00.004+01:002020-12-10T17:03:41.961+01:00You have reached your pull rate limit!<p>Ahh, yes! </p><blockquote><p>You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit</p></blockquote><p>I bet you had encountered this problem if you read now this post. According to <a href="https://www.docker.com/increase-rate-limits" target="_blank">this document</a>:</p><blockquote><p>Beginning November 2, 2020, progressive enforcement of rate limits for anonymous and authenticated Docker Hub usage goes into effect. This means that anonymous and free Docker Hub users will have usage restrictions gradually placed on container image pull requests.</p></blockquote><p>Sadly, this happened on our clusters (AWS EKS). How to fix it? I wanted to spawn <i>DaemonSet</i> object where I could run <i>docker login</i> command and this way change <i>config.json</i> on every node. But, after that you need to restart <i>docker</i> process on every node, and I still do not know how to do that on AWS EKS. So, a temporary fix was to create a <i>Secret</i> object and then link it to every <i>ServiceAccount</i> object.</p><p>It is "a hack", but we needed very fast working solution. We are not going to keep this of course. If you also need to do that fast, here are the commands:</p>
<script src="https://gist.github.com/tomislater/8f71eae7f22c7dac8bf9037bc6ea4bdb.js"></script>
<p><br /></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-63443259139628551612020-11-18T13:24:00.005+01:002021-01-28T10:11:44.335+01:00GitLab - rules keyword<p style="text-align: left;">Today we are going to learn more about the <i>rules</i> keyword in GitLab. Normally, when you want to spawn your job on a specific branch or only in merge requests you can just use the <i>only/except</i> keywords. But, sometimes it is not enough. For example, what if you want to spawn a job always on <i>develop</i> branch and run it automatically, but in <i>merge requests</i> you want to run it manually? You cannot (easily) do that with the <i>only/except</i> keywords.</p><p style="text-align: left;">Excerpt from <a href="https://docs.gitlab.com/ee/ci/yaml/#rules" target="_blank">GitLab documentation</a> about the <i>rules</i> keyword:</p><div><div></div><blockquote><div>The <i>rules</i> keyword can be used to include or exclude jobs in pipelines. </div></blockquote><blockquote><div>Rules are evaluated in order until the first match. When matched, the job is either included or excluded from the pipeline, depending on the configuration. If included, the job also has certain attributes added to it. </div></blockquote><blockquote><div>rules replaces only/except and can’t be used in conjunction with it. If you attempt to use both keywords in the same job, the linter returns a key may not be used with rules error.</div></blockquote><p>There are two very important things about this keyword:</p><p></p><ul style="text-align: left;"><li>either you use <i>rules</i> or <i>only/except</i>, you cannot use both of them</li><li>you can add two attributes to the <i>rules</i> keyword:</li><ul><li><i>when</i></li><li><i>allow_failure</i></li></ul></ul><p style="text-align: left;">So, the <i>rules</i> keyword allows us to build more sophisticated pipelines. We are going to build a pipeline with the <i>only/except</i> keywords and then we will build a pipeline with the <i>rules</i> keyword.</p><p style="text-align: left;">Let's say that we want to run linters in merge requests only, build images automatically on develop, staging and master branches, and additionally, we also want to build an image in merge requests, but manually:</p>
<script src="https://gist.github.com/tomislater/e2769b707badc6cb1b04909548474fab.js"></script>
<p style="text-align: left;">Hmm... I do not see the manual build job! Sadly, you cannot change the <i>when</i> keyword if you use the <i>only/except</i> keywords. So, let's resolve the problem with the <i>rules</i> keyword:</p>
<script src="https://gist.github.com/tomislater/3db949509168fb01eb194be53aeba92e.js"></script>
<p style="text-align: left;">You can see two IFs and a default state (remember that rules are evaluated in order until the first match). In the first IF we check if a pipeline is in a merge request. If it is, then we change the value of <i>when</i> to <i>manual</i>. And, user can run this job manually. In the second IF we check if a branch name is develop, staging or master. If it is, we run the job always no matter what. The last step is a default state (you do not have to set up it). We just set up the value of <i>when</i> to <i>never</i>. That's all.</p><p style="text-align: left;">As you can see, your pipelines can be more sophisticated (not complicated!) with the <i>rules</i> keyword.</p><p style="text-align: left;">Here is also video!</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="339" src="https://www.youtube.com/embed/ddor-4Q2xto" width="560" youtube-src-id="ddor-4Q2xto"></iframe></div><br /><p style="text-align: left;"><br /></p></div>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-39481916426871496242020-11-16T13:23:00.002+01:002021-01-28T10:11:57.983+01:00GitLab - extends keyword<p>In <a href="include" target="_blank">the previous post</a> I had written about the <i>include</i> keyword. Now, let's dive in the <i>extends</i> keyword.</p><p>What can you do with this? Here is the definition from GitLab documentation:</p><blockquote><i>extends</i> defines entry names that a job that uses extends inherits from.<br />It’s an alternative to using <a href="https://docs.gitlab.com/ee/ci/yaml/#anchors" target="_blank">YAML anchors</a> and is a little more flexible and readable.</blockquote><p style="text-align: left;">So, when is it useful? It is useful when you want to be <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" target="_blank">DRY</a> and keep your setup cleanly. Let's assume that you want to build a docker image and give developers possibility to deploy the image on <i>dev</i>, <i>staging</i> and <i>prod</i> environments. We are going to do that without the <i>extends</i> keyword, and after that we will think how we can do it better.</p><p style="text-align: left;">Example without the <i>extends</i> keyword:</p>
<script src="https://gist.github.com/tomislater/d36f95e080911ed768b80e7a7af2fc69.js"></script>
<p style="text-align: left;">As you can see, there is a lot of code which repeats itself: <i>stage, image, when</i> and <i>script</i>. We can remove a lot of code! So, let's do that. We are going to create a hidden job <i>.deploy</i> (you will not see this job in a pipeline) and the rest of the jobs will inherit from it:</p>
<script src="https://gist.github.com/tomislater/51a1f65f28597fe3f94d3e105b49da7d.js"></script>
<p style="text-align: left;">Looks much better. This way, when you want to change the logic of the deployment you just need to edit one job. Every other job which uses the <i>extends</i> keyword and use the <i>.deploy</i> job will inherit the rest.</p><p style="text-align: left;">One more thing.</p><p style="text-align: left;">What if you want to send info about deployments to Slack for example? But you only care about <i>staging</i> and <i>prod</i>? You can use the <i>after_script</i> section. We are going to add the logic for this to the <i>.deploy</i> job, but we also want to do that only for <i>staging</i> and <i>prod</i>. We can use one of <a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html" target="_blank">predefined environment variable</a>: <span style="font-family: helvetica;">$CI_ENVIRONMENT_NAME</span><span style="font-family: inherit;"> and check it.</span></p>
<script src="https://gist.github.com/tomislater/125f4c9edca6ed856e3b0f2491a270b7.js"></script>
<p style="text-align: left;">Instead of this, we could have also added the <i>after_script</i> section to <i>deploy staging</i> and <i>deploy prod</i> jobs with removing IF which checks the environment.</p><p style="text-align: left;">I have also added a video:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="369" src="https://www.youtube.com/embed/XJQVRNdnXT4" width="591" youtube-src-id="XJQVRNdnXT4"></iframe></div><br /><p style="text-align: left;"><br /></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-83210550410727997772020-11-14T18:36:00.002+01:002021-01-28T10:11:28.646+01:00GitLab - include keyword<p>Some time ago GitLab has introduced <a href="gitlab," target="_blank">include</a> keyword that: </p><blockquote>allows the inclusion of external YAML files.</blockquote><p>Everyone knows that microservices like to multiply. Thus, this feature is very useful when you have microservices, because you do not have to copy & paste the code. Let's see what can you do with this.</p><p>Let's assume that we use Docker and we need to build an image. If you want to build an image and deploy your microservice you can just make a new <i>.gitlab-ci.yml</i> file in your project and add two jobs: <i>build </i>and <i>deploy</i>:</p>
<script src="https://gist.github.com/tomislater/9e142fe377c6e39073b973774d933408.js"></script>
<p>When you have three/four microservices you can tempt to copy & paste this code to the rest of the projects. But, if you have more projects and you need to change the logic of deployments, then you have a problem, because you need to edit many <i>.gitlab-ci.yml</i> files. You do not want to do that, so how to deal with it?</p><p>Use <i>include</i> keyword!</p><p>It is very simple. For me in most cases creating one gitlab template file is enough. Let's call it <i>gitlab-template.yml</i>. We need to store it somewhere. It does not have to be GitLab itself. You can store the file wherever you want. But I encourage you to use gitlab repo. You will have less problems. Let's create a repository where we are going to keep this file. Other microservices will include this file. Let's call the repo: <i>ci-template</i> and put <i>gitlab-template.yml</i> file on <i>master</i> branch. The content of this file is exactly as the above one.</p><p>Now, we want to include this file in our microservices repositories. We know the name of the repository, the name of the file, and the name of the branch, thus we can set up <i>.gitlab-ci.yml</i> file in our microservice:</p>
<script src="https://gist.github.com/tomislater/0650122079d2f8d4320b8c83b39e2f5d.js"></script>
<p>In this way, when you create a new pipeline, your microservice project will merge the content of the file from <i>ci-template</i> project to <i>.gitlab-ci.yml</i> file.</p><p>Let's say that you want to add another step to your pipeline: tests. But, we do not have tests in all projects yet and a command for tests is not always the same. Can we still use <i>include</i> keyword? Of course!</p><p>We can add another section to <i>gitlab-template.yml</i> (the <i>test</i> section) file:</p>
<script src="https://gist.github.com/tomislater/040c764d546d463c417b5bfd04e2cd90.js"></script>
<p>We pull an image and run linter in the <i>before_script</i> section. Additionally, in the <i>script</i> section, by default we just print information that you have not added any tests yet. If you want to add tests to your project you should overwrite the <i>script</i> section. How to do that? Remember that you can overwrite every job in your <i>.gitlab-ci.yml</i> file (which are "inherited" from <i>gitlab-template.yml</i> file). So, you can edit the <i>script</i> section:</p>
<script src="https://gist.github.com/tomislater/a825db7ba51ce30cff89b154cceff263.js"></script>
<p>Yeah, everything will be the same, you just changed the <i>script</i> section, nothing else. The job will pull an image, run linter (the <i>before_script</i> section) and run your tests without printing info that you have not added any tests because you overwrote the <i>script</i> section.</p><p>And that's it.</p><p>Video bonus:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="370" src="https://www.youtube.com/embed/7UMiBG1YHYE" width="594" youtube-src-id="7UMiBG1YHYE"></iframe></div><br /><p><br /></p>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0Wrocław, Poland51.1078852 17.03853768.7581739690637121 -53.2739624 90 87.3510376tag:blogger.com,1999:blog-2534243912637235591.post-44182244582531572992016-10-15T15:49:00.000+02:002016-10-15T15:49:03.361+02:00Bug in (datetime|time).strptime - AttributeError: _strptime<h2>
Issue 7980</h2>
<br />
This <a href="https://bugs.python.org/issue7980" target="_blank">bug</a> occurs only when you use threads and only once.<br />
<br />
<script src="https://gist.github.com/tomislater/3ebb983563759ce4cfd0c0759ba4a7f1.js"></script>
Of course, this method is thread safe, but there is a severe warning. The first use of <i style="background-color: #cccccc;">strptime</i> is not thread secure (underneath <i style="background-color: #cccccc;"><a href="https://hg.python.org/cpython/file/default/Lib/_strptime.py" target="_blank">_strptime</a></i> is imported, and the import may throw <span style="background-color: #cccccc;"><i>AttributeError</i></span>). If you want to avoid this problem, either you have to call <i style="background-color: #cccccc;">strptime</i> or import <span style="background-color: #cccccc;"><i>_strptime</i></span> before starting a thread.<br />
<br />
<h2>
How to fix it?</h2>
<div>
Just import <i style="background-color: #cccccc;">_strptime</i> or call <span style="background-color: #cccccc;"><i>strptime</i></span> before starting a thread.</div>
<div>
<br />
<script src="https://gist.github.com/tomislater/068ce5c7f8543d86eb4998c3fc19375f.js"></script></div>
It seems that it happens since 2010.
tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-83553846810909129302016-07-31T21:20:00.000+02:002016-07-31T21:20:13.327+02:00Py.test - Splitting conftest fileIf you have to maintain a massive project, you probably have many fixtures in conftest file. And there is a problem; this file grows and grows. So, at some point, you decide to split this huge file into smaller files. But, py.test has to know the fixtures are keeping in these files.<br />
<br />
So, what you can do? I see three patterns here for this:<br />
<ul>
<li>import these guys inside <i><span style="background-color: #f3f3f3;">conftest.py</span></i> file</li>
<li>create more <span style="background-color: #f3f3f3;"><i>conftest.py</i></span> files</li>
<li>use <span style="background-color: #f3f3f3;"><i>pytest_plugins</i></span></li>
</ul>
<br />
<h2>
Import fixtures inside conftest file </h2>
<br />
You can for example do this:
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3
4
5
6
7
8
9</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">tests.my_fixtures.fixs1</span> <span style="color: #008800; font-weight: bold;">import</span> (
fix11, fix12, fix13, fix14, fix15, ...</pre>
<pre style="line-height: 125%; margin: 0;">)
<span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">tests.my_fixtures.fixs2</span> <span style="color: #008800; font-weight: bold;">import</span> (
fix21, fix22, fix23,
)
<span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">tests.my_fixtures.fixs3</span> <span style="color: #008800; font-weight: bold;">import</span> (
fix31, fix32, fix33
)
</pre>
</td></tr>
</tbody></table>
</div>
<br />
But, there are some problems with this approach. You import them in an explicit way, so if you create a new fixture, you have to remember to add this guy in the import clause. So, this strategy is not the best solution. If you have many files with fixtures, it might hurt you. Every developer will have to remember that he/she has to append this fixture here.<br />
<br />
Hence, you can import all of them:<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">tests.my_fixtures.fixs1</span> <span style="color: #008800; font-weight: bold;">import</span> <span style="color: #333333;">*</span>
<span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">tests.my_fixtures.fixs2</span> <span style="color: #008800; font-weight: bold;">import</span> <span style="color: #333333;">*</span>
<span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">tests.my_fixtures.fixs3</span> <span style="color: #008800; font-weight: bold;">import</span> <span style="color: #333333;">*</span>
</pre>
</td></tr>
</tbody></table>
</div>
<br />
But, I don't think it is a good practice. For example, you can encounter some problems with <a href="https://github.com/PyCQA/pyflakes" target="_blank">pyflakes</a>.<br />
<br />
<h2>
Create more conftest files</h2>
<br />
You could also create more conftest files. A straightforward solution is to separate tests for example into unit/integrations/ui and keep conftest files there.<br />
<br />
For example:
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3</pre>
</td><td><pre style="line-height: 125%; margin: 0;">tests/ui/conftest.py
tests/unit/conftest.py
tests/integration/conftest.py
</pre>
</td></tr>
</tbody></table>
</div>
<br />
Ok, but what if these files are still large? You want to split them again... We've discovered recently that we can use <i><span style="background-color: #f3f3f3;">pytest_plugins</span></i> for this.<br />
<br />
<h2>
Use pytest_plugins</h2>
<br />
How py.test discovers plugins (<a href="https://pytest.org/dev/plugins.html#plugin-discovery-order-at-tool-startup" target="_blank">here's</a> more info)?<br />
<ol>
<li>by loading all built-in plugins</li>
<li>by loading all plugins registered through setuptools entry point</li>
<li>by pre-scanning the command line for the <i><span style="background-color: #f3f3f3;">-p name</span></i> option</li>
<li>by loading all <i><span style="background-color: #f3f3f3;">conftest.py</span></i> files</li>
<li>by recursively loading all plugins specified by the <span style="background-color: #f3f3f3;"><i>pytest_plugins</i></span> variable in <span style="background-color: #f3f3f3;"><i>conftest.py</i></span> files</li>
</ol>
Look at the fifth item; you can load your fixtures by the <i><span style="background-color: #f3f3f3;">pytest_plugins</span></i> variable. You just have to add the <span style="background-color: #f3f3f3;"><i>pytest_plugins</i></span> variable to your <span style="background-color: #f3f3f3;"><i>conftest.py</i></span> file.<br />
<br />
Let's say you had many fixtures in UI tests (in a <span style="background-color: #f3f3f3;"><i>conftest.py</i></span> file). You split them into smaller modules, and now you want to have access to all of them during tests.<br />
<br />
Here's example:
<!-- HTML generated using hilite.me --><br />
<div style="background: #f8f8f8; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2
3
4
5
6
7
8</pre>
</td><td><pre style="line-height: 125%; margin: 0;">pytest_plugins <span style="color: #666666;">=</span> [
<span style="color: #666666;">...</span>
<span style="color: #ba2121;">'tests.ui.fixtures.user_fixtures'</span>,
<span style="color: #ba2121;">'tests.ui.fixtures.admin_fixtures'</span>,
<span style="color: #ba2121;">'tests.ui.fixtures.template_fixtures'</span>,
<span style="color: #ba2121;">'tests.ui.fixtures.free_account_fixtures'</span>,
<span style="color: #666666;">...</span>
]
</pre>
</td></tr>
</tbody></table>
</div>
<br />
If you create a new module with new fixtures, you have to add this guy here, but I think it is not a problem.
tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-22036939829001058262016-06-27T23:40:00.000+02:002016-06-27T23:40:18.273+02:00DynamoDB in pytest-dbfixtures<h2>
pytest-dbfixtures</h2>
If you use pytest maybe you have heard about <a href="https://github.com/ClearcodeHQ/pytest-dbfixtures" target="_blank">pytest-dbfixtures</a>:<br />
<blockquote class="tr_bq">
Pytest dbfixtures is a pytest plugin that makes it a lot easier
to set up proper database or storage engine for testing. Simply use one of
provided fixtures that start predefined clean database server for your
tests or creates server more tailored for your application by using
one of provided factories.</blockquote>
This plugin is very useful if you have integration tests in your project, and you want to perform tests on a database for example. You will find information how to use it in the <a href="http://pytest-dbfixtures.readthedocs.io/en/latest/howtouse.html" target="_blank">documentation</a>.<br />
<br />
Currently, the plugin supports:<br />
<ul>
<li>Postgresql</li>
<li>MySQL</li>
<li>Redis</li>
<li>Mongo</li>
<li>Elasticsearch</li>
<li>RabbitMQ</li>
</ul>
And recently, we have added support for <a href="https://aws.amazon.com/dynamodb/" target="_blank">DynamoDB</a>.<br />
<br />
<a href="http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html" target="_blank">Here</a>,
you will find how to run DynamoDB on your computer. And, here we are.
If you want to use it in production, you want to test it locally.<br />
<br />
<h2>
dynamodb fixture</h2>
If you still do not use pytest, go to the <a href="http://pytest.org/">pytest page</a> and read how to use it, now. If you want to understand the further content you have to know how pytest works.<br />
<br />
The simplest way is to put <span class="pl-smi"><i>dynamodb</i> fixture as an argument in your test:</span>
<br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;">1
2</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">test_dynamodb</span>(dynamodb):
<span style="color: #333333;">...</span>
</pre>
</td></tr>
</tbody></table>
</div>
<span class="pl-smi"><br /></span> <span class="pl-smi">You will find an instance of the class <a href="http://boto3.readthedocs.io/en/latest/reference/services/dynamodb.html#DynamoDB.ServiceResource">DynamoDB.ServiceResource</a> in dynamodb variable.</span> The plugin assumes you have extracted DynamoDB files<span class="pl-smi"> into </span><span class="pl-s"><i>/tmp/dynamodb</i>. If you have not extracted the files into this directory, or you do not want to do this, you can create an individual fixture:</span>
<br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">pytest_dbfixtures</span> <span style="color: #008800; font-weight: bold;">import</span> factories
my_dynamodb_proc <span style="color: #333333;">=</span> factories<span style="color: #333333;">.</span>dynamodb_proc(
dynamodb_dir<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"/path/to/mydynamodb"</span>,
port<span style="color: #333333;">=</span><span style="color: #0000dd; font-weight: bold;">14235</span>,
delay<span style="color: #333333;">=</span><span style="color: #007020;">True</span>,
)
my_dynamodb <span style="color: #333333;">=</span> factories<span style="color: #333333;">.</span>dynamodb(<span style="background-color: #fff0f0;">"my_dynamodb_proc"</span>)
<span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">test_my_dynamodb</span>(my_dynamodb):
<span style="color: #333333;">...</span>
</pre>
</td></tr>
</tbody></table>
</div>
<span class="pl-s"><br /></span>As you can see, you may also pass <i>port</i> and <i>delay</i> as arguments. If you do not specify the port argument, the fixture will use a random port. If you want to introduce delays for certain operations in DynamoDB you can pass the argument <i>delay</i> as a <i>True</i>.<br />
<blockquote class="tr_bq">
DynamoDB can perform some tasks
almost instantaneously, such as create/update/delete operations on tables
and indexes; however, the actual DynamoDB service requires more time for these
tasks. Setting this parameter helps DynamoDB simulate the behavior of the
Amazon DynamoDB web service more closely. (Currently, this parameter introduces delays only for
global secondary indexes that are in either <span class="emphasis"><i>CREATING</i></span> or
<span class="emphasis"><i>DELETING</i></span> status.) </blockquote>
So, let's say that you want to run DynamoDB on 9009 port with some delays, and you keep DynamoDB files in <i>/tmp/tests/dynamodb</i>. Then you want to create a table and put some data there.<br />
<br />
Here is an example:<br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">pytest_dbfixtures</span> <span style="color: #008800; font-weight: bold;">import</span> factories
my_dynamodb_proc <span style="color: #333333;">=</span> factories<span style="color: #333333;">.</span>dynamodb_proc(
dynamodb_dir<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"/tmp/tests/dynamodb"</span>,
port<span style="color: #333333;">=</span><span style="color: #0000dd; font-weight: bold;">9009</span>,
delay<span style="color: #333333;">=</span><span style="color: #007020;">True</span>,
)
my_dynamodb <span style="color: #333333;">=</span> factories<span style="color: #333333;">.</span>dynamodb(<span style="background-color: #fff0f0;">"my_dynamodb_proc"</span>)
<span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">test_my_dynamodb</span>(my_dynamodb):
table <span style="color: #333333;">=</span> my_dynamodb<span style="color: #333333;">.</span>create_table(
TableName<span style="color: #333333;">=</span><span style="background-color: #fff0f0;">"Test"</span>,
KeySchema<span style="color: #333333;">=</span>[
{
<span style="background-color: #fff0f0;">"AttributeName"</span>: <span style="background-color: #fff0f0;">"id"</span>,
<span style="background-color: #fff0f0;">"KeyType"</span>: <span style="background-color: #fff0f0;">"HASH"</span>
}
],
AttributeDefinitions<span style="color: #333333;">=</span>[
{
<span style="background-color: #fff0f0;">"AttributeName"</span>: <span style="background-color: #fff0f0;">"id"</span>,
<span style="background-color: #fff0f0;">"AttributeType"</span>: <span style="background-color: #fff0f0;">"N"</span>
}
],
ProvisionedThroughput<span style="color: #333333;">=</span>{
<span style="background-color: #fff0f0;">"ReadCapacityUnits"</span>: <span style="color: #0000dd; font-weight: bold;">10</span>,
<span style="background-color: #fff0f0;">"WriteCapacityUnits"</span>: <span style="color: #0000dd; font-weight: bold;">10</span>
}
)
table<span style="color: #333333;">.</span>put_item(
Item<span style="color: #333333;">=</span>{
<span style="background-color: #fff0f0;">"id"</span>: <span style="color: #0000dd; font-weight: bold;">42</span>,
<span style="background-color: #fff0f0;">"my_data"</span>: <span style="background-color: #fff0f0;">"is secret"</span>
}
)
response <span style="color: #333333;">=</span> table<span style="color: #333333;">.</span>get_item(
Key<span style="color: #333333;">=</span>{
<span style="background-color: #fff0f0;">"id"</span>: <span style="color: #0000dd; font-weight: bold;">42</span>,
}
)
<span style="color: #008800; font-weight: bold;">assert</span> response[<span style="background-color: #fff0f0;">"Item"</span>][<span style="background-color: #fff0f0;">"my_data"</span>] <span style="color: #333333;">==</span> <span style="background-color: #fff0f0;">"is secret"</span>
</pre>
</td></tr>
</tbody></table>
</div>
<br />
Because we want to test faster and faster, we run DynamoDB in memory.<span class="pl-s"> Remember that when you stop the process of DynamoDB, none of the data will be saved (DynamoDB does not write any database files at all). If you do not want to run tests in memory, your Pull Request is welcome.</span>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-25045247342140049672016-06-07T09:14:00.000+02:002016-06-07T09:14:12.750+02:00PyCharm - tips, tricks and plugins<h2>
Introduction</h2>
I have been using Vim for a long time, but recently I have started using PyCharm too. This tool has many proper features and plugins. I will show you some of them. You will find here many gifs instead of text. In this way, you can easily see how it works, how it looks, etc.<br />
<br />
<h2>
Plugins</h2>
<br />
<b>.ignore</b><br />
This plugin was written by my friend. <a href="https://github.com/hsz/idea-gitignore#usage" target="_blank">Here</a> is more info about it. It will generate new ignore files for you.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhf5RdIvKbVXesvtT9E8bu1Da4l9xugG3LADQKNlMajpknJb9fNbA0L5RCblgZF0mYLSXLiUR_RfBZakGFoRDbADAUrTQnUVbLlIzJO05D6esrAX4crTh8aRvh9c-z2_ZpKGIPHys3oQz4/s1600/ignore.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhf5RdIvKbVXesvtT9E8bu1Da4l9xugG3LADQKNlMajpknJb9fNbA0L5RCblgZF0mYLSXLiUR_RfBZakGFoRDbADAUrTQnUVbLlIzJO05D6esrAX4crTh8aRvh9c-z2_ZpKGIPHys3oQz4/s1600/ignore.gif" /></a></div>
<br />
<br />
<b>BashSupport</b><br />
Supports syntax highlighting, rename refactoring, documentation lookup, inspections, quickfixes, etc.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMnTKCMhBAs1VR_MSdyziYRkO4jW2YK7sGGnGa5ToHvVkfcIpDkLtm3rgzKJ0Ekdz88E47rb9ncEAfMDN66Pr-qVB8SSbpMriFF5ERONi6rcGd_u9pWy2gVYhnXzJtYxPMV_Eh-QTnTCw/s1600/bashsupport.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMnTKCMhBAs1VR_MSdyziYRkO4jW2YK7sGGnGa5ToHvVkfcIpDkLtm3rgzKJ0Ekdz88E47rb9ncEAfMDN66Pr-qVB8SSbpMriFF5ERONi6rcGd_u9pWy2gVYhnXzJtYxPMV_Eh-QTnTCw/s1600/bashsupport.gif" /></a></div>
<br />
<ul>
</ul>
<b>Dash</b><br />
A smart and simple plugin that provides keyboard shortcut access for <a href="https://kapeli.com/dash" target="_blank">Dash</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_Ei9AwcZj-vEn0cCWHP0CpMBy7MgyYi7C4yRtP5MC44FePUqeDPG78Zq1V1idGx6yygx5rGuOTpdm-JonOmBrZyzn80PNiSHYvP9ljH-BAKQ2U9wu2kttt3eebVpJhDMDRx0xBQEdR8Y/s1600/dash.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_Ei9AwcZj-vEn0cCWHP0CpMBy7MgyYi7C4yRtP5MC44FePUqeDPG78Zq1V1idGx6yygx5rGuOTpdm-JonOmBrZyzn80PNiSHYvP9ljH-BAKQ2U9wu2kttt3eebVpJhDMDRx0xBQEdR8Y/s1600/dash.gif" /></a></div>
<br />
<b>KeyPromoter</b><br />
Shows you how easy you can make the same action using only a keyboard.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH9OqJ_qwax5UJKXr454R05Y812o9ll3sDGnxz_JQk2JN9JJWax4J5jWISrHVQF7WtGJ8k1UN4EOE674fS86bOohictwRcpR_FqpTeO1l7_K9DRqZ3qf2lN6k5TTwDXNYm84qprorNt0M/s1600/keypromoter.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH9OqJ_qwax5UJKXr454R05Y812o9ll3sDGnxz_JQk2JN9JJWax4J5jWISrHVQF7WtGJ8k1UN4EOE674fS86bOohictwRcpR_FqpTeO1l7_K9DRqZ3qf2lN6k5TTwDXNYm84qprorNt0M/s1600/keypromoter.gif" /></a></div>
<br />
<b>Lua</b><br />
Support for LUA.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXZ1uoAzz8iwaDcy61Jswqy5myd6dxnPK7cG5vv2nGwj3t2o7yoyWzFEG7EfDXhCzHnKXBrhlpByxwz03ZXu5P0hF2K0nuslaBWSyzDXEdibZUejOaJJiYWsumiT8xtXylPiGm7xVNmjg/s1600/lua.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXZ1uoAzz8iwaDcy61Jswqy5myd6dxnPK7cG5vv2nGwj3t2o7yoyWzFEG7EfDXhCzHnKXBrhlpByxwz03ZXu5P0hF2K0nuslaBWSyzDXEdibZUejOaJJiYWsumiT8xtXylPiGm7xVNmjg/s1600/lua.gif" /></a></div>
<br />
<b>Markdown</b><br />
Support for markdown.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoJ-2nVtosmI1IfPtJxjqvilHlJvXs7JiqFCmI8znUXsa4Q9PTbG9FIv4XVhMErddbjY4r1odCrFHmoVAn51wrQRx3uJCRydelr3iphVpQOoVNqZbXD7iB5LTTlrknxf_nesZomQdrGMk/s1600/markdown.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoJ-2nVtosmI1IfPtJxjqvilHlJvXs7JiqFCmI8znUXsa4Q9PTbG9FIv4XVhMErddbjY4r1odCrFHmoVAn51wrQRx3uJCRydelr3iphVpQOoVNqZbXD7iB5LTTlrknxf_nesZomQdrGMk/s1600/markdown.gif" /></a></div>
<br />
<b>YAML/Ansible support</b><br />
I don't want to write again "Support for YAML/Ansible".<br />
<br />
<h2>
Tips & Tricks </h2>
<br />
You do not know what to do? Do not be scared! Just use TODO.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzIROo213GE1wP5lA0d2XTekx2AsfArlQJZYw0pwyAXZGZ1GLG_77MD-ZvAkWYQ1N1JbBPL_iXgjJsk3p_nUZelnKaO4gK-aSrVhprk94HuSOaBw4zXhtIWsuCzoaJ8boamj_LjdFJKuo/s1600/todo-feature.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzIROo213GE1wP5lA0d2XTekx2AsfArlQJZYw0pwyAXZGZ1GLG_77MD-ZvAkWYQ1N1JbBPL_iXgjJsk3p_nUZelnKaO4gK-aSrVhprk94HuSOaBw4zXhtIWsuCzoaJ8boamj_LjdFJKuo/s1600/todo-feature.gif" /></a></div>
<br />
You have access to Terminal and Python Console in an easy way. Of course, you can decide which shell do you want to use.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYmwgXsYlO3ROWoaeP4g754MP4L0isyp8F7Cw-w5eEs1MLo-Dr8tfcVurcrL4uARb2xqXcmbgXyGy3YsCkQOlerg5E7t4zYuYP0MDUPSaDDFl2UALxesaUrxQDMDuQoi7BUdAL9rO4Y9c/s1600/terminal-python-console.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYmwgXsYlO3ROWoaeP4g754MP4L0isyp8F7Cw-w5eEs1MLo-Dr8tfcVurcrL4uARb2xqXcmbgXyGy3YsCkQOlerg5E7t4zYuYP0MDUPSaDDFl2UALxesaUrxQDMDuQoi7BUdAL9rO4Y9c/s1600/terminal-python-console.gif" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdJni8hV2nO0qOfhWjCRpkoRJCK21iUQjpjaugBy5MzjlHBaI-pDESdLjAqKlLsFGrj4yyJG9IIE_mevlP-1RduMoHceccM8g2EWzettfs4hKXvKzQcXhyphenhyphen9a58RhTfJmIU2eeNF61zU3s/s1600/Zrzut+ekranu+2016-06-06+o+8.37.48+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdJni8hV2nO0qOfhWjCRpkoRJCK21iUQjpjaugBy5MzjlHBaI-pDESdLjAqKlLsFGrj4yyJG9IIE_mevlP-1RduMoHceccM8g2EWzettfs4hKXvKzQcXhyphenhyphen9a58RhTfJmIU2eeNF61zU3s/s400/Zrzut+ekranu+2016-06-06+o+8.37.48+AM.png" width="400" /></a></div>
<br />
Version Control. Let's say you want to see what someone has changed in this file from commit A to commit B. You just see a history of git.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzCANrJn_1jiRowv2MCJwMBjRP3h-IEkjkG0yRhvLjqJ_2uMeLuu0TSg3TRXbib6Qqlt0ZUEPfygsF7RawBLLoI9PX7EuIt66PfCJFchO5gpH4LvsUwtIaDsKLIT7O3vEL3klKVy-XRfw/s1600/version-control-history.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzCANrJn_1jiRowv2MCJwMBjRP3h-IEkjkG0yRhvLjqJ_2uMeLuu0TSg3TRXbib6Qqlt0ZUEPfygsF7RawBLLoI9PX7EuIt66PfCJFchO5gpH4LvsUwtIaDsKLIT7O3vEL3klKVy-XRfw/s1600/version-control-history.gif" /></a></div>
<br />
And now, you want to see all commits of a particular user.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiz07wiUCj5p3Yfce-gZmnrawFwgp3g5nR2E0sSVRDzZIb_xBmLhVKL0uME62uCRbXoYHbaAbxif0aq8Sxedal2Ns_h0K6yNQwBF_qoczYexKP65Ji_Wbgsv4mvdzyDi1aKpnL7zSY2CA/s1600/version-control-log.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiz07wiUCj5p3Yfce-gZmnrawFwgp3g5nR2E0sSVRDzZIb_xBmLhVKL0uME62uCRbXoYHbaAbxif0aq8Sxedal2Ns_h0K6yNQwBF_qoczYexKP65Ji_Wbgsv4mvdzyDi1aKpnL7zSY2CA/s1600/version-control-log.gif" /></a></div>
<br />
You have reached a very odd file. You want to see who has made this, when and why.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDvFDzazYkwOV4mf6eaMTCeMpMcJ08LCQCQErhqeMuGRKIr0IEyASE4_f-JOZmzxYjm9V1w-ui-cM_pnHhfuRD51dog8wHUxWmyjoTdap5b7Q0GYKtl_CUeYNVFFxfXUFMFpbWvfB2-jM/s1600/version-control-annotate.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDvFDzazYkwOV4mf6eaMTCeMpMcJ08LCQCQErhqeMuGRKIr0IEyASE4_f-JOZmzxYjm9V1w-ui-cM_pnHhfuRD51dog8wHUxWmyjoTdap5b7Q0GYKtl_CUeYNVFFxfXUFMFpbWvfB2-jM/s1600/version-control-annotate.gif" /></a></div>
<br />
You have a better idea to name a variable, so you want to change this quickly.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBwU-_VFpRPe3zGBSd05JQygXOCCMMsuW3FEn0xEZgDiZ_ePuura92Mdpze4mfcsHRCI2lin_H8od2ajkPCBINX40HR21T1GK1wFnkad1q0gvuttPlBcgSL51rF7cbsWTbkTFn1Er2tMw/s1600/refactor-rename.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBwU-_VFpRPe3zGBSd05JQygXOCCMMsuW3FEn0xEZgDiZ_ePuura92Mdpze4mfcsHRCI2lin_H8od2ajkPCBINX40HR21T1GK1wFnkad1q0gvuttPlBcgSL51rF7cbsWTbkTFn1Er2tMw/s1600/refactor-rename.gif" /></a></div>
<br />
If you create a docstring PyCharm will fill the docstring with parameters.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhKktWf7jBHY4t2u0ckxSmHtCEhVx07gsu2qgvw8pujNeXRZoFt34-3Rt8CCQLtJjOg5ZDDkNKJlkdD5AppLQgJNroYS48mWk93c5I16DebxoC16WIxqD4zg6W8Hzc8R9TJvt2muo_Tfc/s1600/docs.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhKktWf7jBHY4t2u0ckxSmHtCEhVx07gsu2qgvw8pujNeXRZoFt34-3Rt8CCQLtJjOg5ZDDkNKJlkdD5AppLQgJNroYS48mWk93c5I16DebxoC16WIxqD4zg6W8Hzc8R9TJvt2muo_Tfc/s1600/docs.gif" /></a></div>
<br />
Let's say that you have changed some lines of code, and you forgot what was there before. Consequently, you want to see these changes and restore to the former state if there was a mistake.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMZ02hZ8OqFjg2UyAPzOgfxka0G13RU8PhoDQtfJaLihiB0r31Nt_EgbLQnuJz3NUfn4w_UER2uj_qK5AvVqe2T8VJijc0OKmcJdrFGngqgZoyYorTDu4zBRlbnVrnWyT3FDWzk2B6ELI/s1600/git-changes-rollback.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMZ02hZ8OqFjg2UyAPzOgfxka0G13RU8PhoDQtfJaLihiB0r31Nt_EgbLQnuJz3NUfn4w_UER2uj_qK5AvVqe2T8VJijc0OKmcJdrFGngqgZoyYorTDu4zBRlbnVrnWyT3FDWzk2B6ELI/s1600/git-changes-rollback.gif" /></a></div>
<br />
Your project is large and you do not want to remember where lives every piece of code. Too much waste of time. Hence, you want to find these objects very quickly. You can locate them by class name, file name, symbol name, etc.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8s4vWM9iI4jaU-AIZB1jONcCrS6jyoyd5lcZ74im94ZXMP-61Hs1V7BSx8duCvYxhlbJ8lGg43o0SQX8uaiLwllQRc8zM5eWjdJkzcNLTtkm0uraTjhrHqIN4P-0KU0uOcFyTe1zvjEA/s1600/find-objects.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8s4vWM9iI4jaU-AIZB1jONcCrS6jyoyd5lcZ74im94ZXMP-61Hs1V7BSx8duCvYxhlbJ8lGg43o0SQX8uaiLwllQRc8zM5eWjdJkzcNLTtkm0uraTjhrHqIN4P-0KU0uOcFyTe1zvjEA/s1600/find-objects.gif" /></a></div>
<br />
You have the class A. Other classes inherit from the class A. In class A, you have a particular method, and you want to see how this method is implemented in other classes.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh40a9eNuX9h-IgOAN0NBN3zuiUxdy9fm-5rw-dFydY6_wwuiLrNaL9Xd2ln2fTGF8nL5WjeHFNSbPXCIPo_YMVqPzY6UbyCf79OGUSpCiSTYLRwFRuf3Wsmi0MnGHGpiN4hdvzbBu96Es/s1600/classes-methods-inherit.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh40a9eNuX9h-IgOAN0NBN3zuiUxdy9fm-5rw-dFydY6_wwuiLrNaL9Xd2ln2fTGF8nL5WjeHFNSbPXCIPo_YMVqPzY6UbyCf79OGUSpCiSTYLRwFRuf3Wsmi0MnGHGpiN4hdvzbBu96Es/s1600/classes-methods-inherit.gif" /></a></div>
<br />
That's enough for me. I am not going to convince someone to use PyCharm; I just wanted show some features that I like very much in this editor. It is the best alternative for Vim (for me of course).<br />
<ul>
</ul>
tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0Wrocław, Polska51.1078852 17.03853760000004150.948439199999996 16.715814100000042 51.2673312 17.361261100000039tag:blogger.com,1999:blog-2534243912637235591.post-13760092913460303882016-06-02T08:08:00.000+02:002016-06-02T23:05:58.784+02:00AWS Lambda and Python<h3>
What is the AWS Lambda?</h3>
<blockquote class="tr_bq">
AWS Lambda lets you run code without provisioning or managing servers. You pay only for the compute time you consume - there is no charge when your code is not running.</blockquote>
So, you can build a scalable application without managing servers. Something like a microservice without servers. Serverless!<br />
<br />
Hence, your function must be written in a stateless style. If you want to store something somewhere, you can connect to S3, Redshift, DynamoDB, etc. AWS Lambda will start to execute your code within milliseconds.<br />
<br />
I am not going to write a tutorial step by step. You will find here only a handful necessary information.<br />
<br />
<h3>
Limits</h3>
Let's look at the <a href="http://docs.aws.amazon.com/lambda/latest/dg/limits.html">limits</a> (all limits are default, you can ask guys from AWS to increase them).<br />
<ul>
<li>You have access to an ephemeral disk with limit 512 MB (access to <i>/tmp</i> only).</li>
<li>Your function must be finished within 300 seconds (it is a default max value; if you want you can set 10 seconds also); if not AWS Lambda will terminate it.</li>
<li>Zip package with your function must be less than 50 MB.</li>
<li>Uncompressed zip must be less than 250 MB.</li>
<li>Memory; you can choose from 128 MB to 1536 MB (you choose more memory, you will pay more; <a href="https://aws.amazon.com/lambda/pricing/">pricing details</a>)</li>
</ul>
Tip: remember to remove not used files from <i>/tmp</i>. AWS Lambda can spawn in "the same box" (if you call many of them in a short period), so you do not want to get the error: "Sorry. Not enough space".<br />
<blockquote class="tr_bq">
The Lambda free tier does not automatically expire at the end of your 12 month AWS Free Tier term, but is available to both existing and new AWS customers indefinitely.</blockquote>
What does it mean? It means that you have 1M free requests per month and 400000 GB-seconds of compute time per month. It will reduce your costs a lot.<br />
<br />
<h3>
The flow</h3>
<ol>
<li>Upload your code to AWS Lambda</li>
<li>Set up your code (when to trigger your code, etc.)</li>
<li>Something triggers your code</li>
<li>You <a href="https://aws.amazon.com/lambda/pricing/" target="_blank">pay</a> only for the compute time you use </li>
</ol>
<br />
<ol>
</ol>
<h3>
Asynchronous and Synchronous</h3>
AWS Lambda can work in two modes. In Asynchronous mode, AWS Lamba is triggered by events in AWS Services and a caller does not wait. In synchronous mode, you trigger the code, and you wait for the result.<br />
<br />
<h3>
Python</h3>
Which versions of Python are supported?<br />
<blockquote class="tr_bq">
Lambda provides a Python 2.7-compatible runtime to execute your Lambda functions. Lambda will include the latest AWS SDK for Python (boto3) by default.</blockquote>
Can I use packages with AWS Lambda in Python?<br />
<blockquote class="tr_bq">
Yes. You can use pip to install any python packages needed. </blockquote>
<br />
<h3>
Programming Model</h3>
<b>Handler</b> - you have to specify a <i>handler</i> function. If something invokes your Lambda function, AWS Lambda will execute your code by calling the handler function.<br />
<br />
<div style="background: #ffffff; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #008800; font-weight: bold;">def</span> <span style="color: #0066bb; font-weight: bold;">handler_function</span>(event, context):
<span style="color: #888888;"># event - some event data (usually dict)</span>
<span style="color: #888888;"># context - runtime information </span>
<span style="color: #008800; font-weight: bold;">return</span> <span style="background-color: #fff0f0;">'ok'</span>
</pre>
</div>
<br />
<b>The Context Object</b> - you will find here some information such as function name, version, memory limit, etc.<br />
<br />
<b>Logging</b> - you can use <i>print</i> or <i>logging</i>. Both variants will produce and put logs into CloudWatch, but with <i>logging</i>, you will have more information like timestamp and log level. For example, you can turn on <i>DEBUG</i> on dev, but <i>WARN</i> on production.<br />
<br />
<b>Exceptions</b> - if your code raises an exception, AWS Lambda will notify you about it. In synchronous mode, AWS Lambda will return you a JSON with information about the exception. In asynchronous mode, you will find the exception in logs.<br />
<br />
<h3>
Versioning and Aliases</h3>
You do not want to put your code directly on production (I merely assume), so you need versioning. It works very smoothly:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNA9lKMfX36t6oumiGNMr0kSKbgzdo7bbu65XjkhDEPwfBWMMrxU7KvRTLPzA_-b19-aeicGIglKt6JegOjB32htoJV8c0yUKsu53BsweRWjZO3B6pP8zL2tLt7GBi2M9ECpHb-pI-kD0/s1600/alias_intro_2_10.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Versioning and Aliases" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNA9lKMfX36t6oumiGNMr0kSKbgzdo7bbu65XjkhDEPwfBWMMrxU7KvRTLPzA_-b19-aeicGIglKt6JegOjB32htoJV8c0yUKsu53BsweRWjZO3B6pP8zL2tLt7GBi2M9ECpHb-pI-kD0/s1600/alias_intro_2_10.png" title="Versioning and Aliases" /></a></div>
<br />
Versions are immutable (you cannot change it), but aliases are mutable. You can think of an alias as a pointer to a particular version. So, your PROD alias can point at Version 1 (stable), but DEV alias can point at Version 5 (in progress). Remember, you cannot change a version; you have to create a new one. But, you can change an alias.<br />
<br />
<h3>
Upload code to AWS Lambda</h3>
Here is a list of packages which make it easier to deploy/update for your function:<br />
<ul>
<li><a href="https://github.com/apex/apex">apex</a></li>
<li><a href="https://github.com/nficano/python-lambda">python-λ</a></li>
<li><a href="https://github.com/rackerlabs/lambda-uploader">lambda-uploader</a></li>
<li><a href="https://github.com/garnaat/kappa">kappa</a></li>
</ul>
I have been using only one of them: lambda-uploader. I have not had any problems with this package.<br />
<br />
<h3>
Asynchronous mode</h3>
Let's say that you have a bucket (S3) where clients can put files, and you want to perform some operations after someone has uploaded a file. You can use AWS Lambda for this. You have to implement a logic in your package and upload it on AWS Lambda. After that, you should create an alias and add event source for this function. You want to call your function when someone uploads a new file to a bucket so that it might look like:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVTXglHCiBAGEmmsRnazOhifXOQj10MWP2IBdg49PEoVpccpQfEScR0a2BOqxualQ9X6YhgTA9QBT1Fj_w04ndshFu5fGQyu1jmYFhVRPC0G6ZP8DR467UvglC-MPlsKEBwNnOGXCDELw/s1600/event-source.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Add event source" border="0" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVTXglHCiBAGEmmsRnazOhifXOQj10MWP2IBdg49PEoVpccpQfEScR0a2BOqxualQ9X6YhgTA9QBT1Fj_w04ndshFu5fGQyu1jmYFhVRPC0G6ZP8DR467UvglC-MPlsKEBwNnOGXCDELw/s400/event-source.png" title="Add event source" width="400" /></a></div>
<br />
Next, if someone put a file in a bucket, AWS Lambda will invoke the function and your code will do something! In monitoring tab you can see stats from the last 24 hours (in CloudWatch you have more data of course):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHRBwvtgpNQukFNwfmiKv1Nw8JU1VocYlhfWz46qMoZPcwA4kHxgZBaJRMgKrbLuV4G-RMoKb49nxwOvnkCOMM_a1pAHPFBOgCP28n2q8PhlnUQmTb3BQAiVnSTfyVnMtOvGDVsIh50ZY/s1600/Zrzut+ekranu+2016-05-29+o+11.02.07+AM.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Monitoring" border="0" height="95" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHRBwvtgpNQukFNwfmiKv1Nw8JU1VocYlhfWz46qMoZPcwA4kHxgZBaJRMgKrbLuV4G-RMoKb49nxwOvnkCOMM_a1pAHPFBOgCP28n2q8PhlnUQmTb3BQAiVnSTfyVnMtOvGDVsIh50ZY/s400/Zrzut+ekranu+2016-05-29+o+11.02.07+AM.png" title="Monitoring" width="400" /></a></div>
<br />
You can create alarms for errors, so if something bad happens, you can intervene.<br />
<br />
If you want, you can also use AWS Lambda as a cron. You have to set this in event source:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwpsRfPQeX2QZuN_ex3S7sglFgm2y0VO7M1ovG1bkbQ4IKoS6eNuaZX-BDLyIuNKeYAhFvpsmlUVTpl_vzqd_A71WUzZjwzSqozL47T_BlNBjtJ-3Z2z7n7TBSQxD9Qj5dJPXgOhZzlxI/s1600/Zrzut+ekranu+2016-06-01+o+8.51.03+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwpsRfPQeX2QZuN_ex3S7sglFgm2y0VO7M1ovG1bkbQ4IKoS6eNuaZX-BDLyIuNKeYAhFvpsmlUVTpl_vzqd_A71WUzZjwzSqozL47T_BlNBjtJ-3Z2z7n7TBSQxD9Qj5dJPXgOhZzlxI/s400/Zrzut+ekranu+2016-06-01+o+8.51.03+AM.png" width="400" /></a></div>
<br />
<h3>
Synchronous mode</h3>
And now, you want to call Lambda function and get a result. Accordingly, you can use synchronous invoke. In this way, you will receive a response if Lambda completes executing (or you will receive JSON with an error). You can use API Gateway and Lambda for this. Here is a <a href="https://github.com/serverless/serverless" target="_blank">framework</a> in BETA stage (2016/05/31) if you are interested.<br />
<br />
<a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-step-by-step.html" target="_blank">Here</a> you will find instruction how to create an API. If you need a tutorial (API Gateway + Lambda), I have found two which are interesting:<br />
<ul>
<li><a href="http://www.giantflyingsaucer.com/blog/?p=5730" target="_blank">Build a Python Microservice with Amazon Web Services Lambda & API Gateway</a></li>
<li><a href="https://blog.fugue.co/2015-10-15-votebot.html" target="_blank">Vote for Pizza with Slack: Python in AWS Lambda</a></li>
</ul>
You can also use events from Kinesis, CloudWatch, DynamoDB and SNS to call AWS Lambda.<br />
<br />
<br />
AWS Lambda is an excellent strategy if you want to perform a specific action from time to time. You have to remember that Lambda is stateless. Therefore, you have to keep information about a state somewhere else (if you need). You do not have to pay for a machine which has to operate all month. Your code runs? You will pay.<br />
<br />
<h3>
Sources:</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=tvp08tJGpm0&ab_channel=AmazonWebServices" target="_blank">AWS Summit Series 2016 | Singapore: Compute without Servers: Building Applications with AWS Lambda</a></li>
<li><a href="https://aws.amazon.com/lambda/?hp=tile" target="_blank">AWS Lambda</a></li>
<li><a href="https://aws.amazon.com/lambda/faqs/" target="_blank">AWS Lambda | FAQs </a></li>
</ul>
tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-2326134381512870712014-01-12T23:24:00.000+01:002014-01-12T23:24:31.562+01:00How you can use watchdog to improve your productivity.<a href="https://github.com/gorakhargosh/watchdog" target="_blank"><b>Watchdog</b></a> is a <i>python API and shell utilities to monitor file system events</i>.<br />
<br />
I am using it for tasks like build sphinx docs or run tests after changes in file/directory. It's very easy to use.<br />
<br />
For instance, you are writing documentation and after each saving file you have to run command like "make html". So, why you don't this automatically?<br />
<br />
Simple command:
<br />
<pre class="prettyprint lang-bsh">watchmedo shell-command --command="make html" --wait source</pre>
What does it? Watchdog began to watch source directory. If something in this directory will be changed, then watchdog will run your command (make html). Simply, right?<br />
<br />
If you use Firefox you can install <a href="https://addons.mozilla.org/en-US/firefox/addon/auto-reload/" target="_blank">Auto Reload</a> add-on. Auto Reload automatically reloads matching tabs when selected local files change. So, after saving file, your documentation will build automatically and Firefox automatically will refresh tab with documentation in browser, brilliant!<br />
<br />
You can also use patterns. For example, you want to run tests after saving file:
<br />
<pre class="prettyprint lang-bsh">watchmedo shell-command --wait --patterns="*.py" \
--command='py.test "${watch_src_path}"' \
--recursive your_project/tests/
# or shorter version
watchmedo shell-command --w -p="*.py" \
-c='py.test "${watch_src_path}"' -R your_project/tests/
</pre>
You will find documentation <a href="http://pythonhosted.org/watchdog/" target="_blank">here</a>. I hope this post will be useful ;) <br />
<br />
tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0Wroclaw, Poland51.1078852 17.03853760000004150.9484387 16.715814100000042 51.2673317 17.361261100000039tag:blogger.com,1999:blog-2534243912637235591.post-71640677860689960862014-01-12T00:11:00.000+01:002014-01-12T10:21:50.254+01:00Hello everyone!From today I am going to write blog in English and also I will write more posts.tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-35598153291031740682013-10-06T20:11:00.001+02:002013-10-06T20:11:30.216+02:00Rzadko wykorzystywane typy danych<h1>
Wstęp</h1>
Przy codziennej pracy z pythonem często korzystasz z takich typów danych jak słownik, lista, krotka czy zbiór. Ale warto pamiętać, że istnieją również inne typy danych, które często mogą Ci się przydać, a o których niekoniecznie słyszałeś. Przedstawię kilka z nich, które moim zdaniem są warte uwagi.<br />
<h2>
Counter</h2>
Typ ten przydaje się wtedy gdy chcesz np. sprawdzić ile razy występuje dany obiekt w zbiorze i dodatkowo chcesz to trzymać jako słownik. Przykład:<br />
<br />
<script src="https://gist.github.com/tomislater/6855354.js"></script>
<br />
<h2>
deque</h2>
Obiekt deque idealnie nadaje się do <a href="https://en.wikipedia.org/wiki/Stack_%28abstract_data_type%29" target="_blank">stosu</a> lub <a href="https://en.wikipedia.org/wiki/Queue_%28abstract_data_type%29" target="_blank">kolejki</a>. Dodawanie oraz pobieranie elementu jest zbliżone do O(1); nie ważne czy pobierasz z końca czy z początku. Jeżeli jednak chcesz dostać się do elementów po indeksach lepiej jest skorzystać z listy, która jest do tego przystosowana, więc siłą rzeczy jest szybsza. Poniżej, krótkie porównanie:<br />
<br />
<script src="https://gist.github.com/tomislater/6855746.js"></script>
<br />
<h2>
defaultdict</h2>
defaultdict przydaje się tam, gdzie chcesz stworzyć słownik w którym przy dostępie ma już mieć zdefiniowaną domyślną wartość. Zamiast martwić się sprawdzaniem czy czasem dany klucz ma zdefiniowaną wartość, sam ustawiasz jaki to ma być typ.<br />
<br />
<script src="https://gist.github.com/tomislater/6855881.js"></script>
<br />
<h2>
namedtuple</h2>
Ten typ danych jest często wykorzystywany przy pobieraniu danych z bazy lub np. z pliku csv. Ja napisałem prosty przykład jak szybko przemienić otrzymanego JSON'a w namedtuple i mieć dostęp do wartości nie po kluczu, a po atrybucie.<br />
<script src="https://gist.github.com/tomislater/6856182.js"></script>
<br />
<h2>
heapq</h2>
heapq przyda się wtedy, gdy chcesz stworzyć <a href="https://en.wikipedia.org/wiki/Heap_%28data_structure%29" target="_blank">kopiec</a> (<a href="https://en.wikipedia.org/wiki/Priority_queue" target="_blank">kolejka priorytetowa</a>). Przykład użycia:<br />
<br />
<script src="https://gist.github.com/tomislater/6856311.js"></script>
<br />
<h2>
bisect</h2>
Wykorzystanie modułu bisect nadaje się wtedy, gdy masz wystarczająco dużą listę, w której sortowanie jej za każdym razem nie wchodzi w grę, ale w jakiś sposób chcesz otrzymać wartość, która znajdowała by się pod indeksem 42, gdyby lista była posortowana. Prosty przykład wraz z porównaniem:<br />
<br />
<script src="https://gist.github.com/tomislater/6857001.js"></script>tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0tag:blogger.com,1999:blog-2534243912637235591.post-25780896674283270682013-09-28T20:11:00.001+02:002013-09-28T21:49:53.075+02:00Pylibmc, ultramemcache i python-memcached<h1>
Wstęp</h1>
W ubiegłym roku pisałem o <a href="http://swiety-python.blogspot.com/2012/12/ultra-szybki-json-czyli-ujson.html" target="_blank">ultrajsonie</a>, który wypadł bardzo dobrze. Przedwczoraj natrafiłem na bibliotekę <a href="https://github.com/esnme/ultramemcache" target="_blank">ultramemcache</a>, którą również postanowiłem przetestować i sprawdzić czy jest ultra.<br />
<br />
Do porównania wykorzystam biblioteki <a href="https://github.com/linsomniac/python-memcached/" target="_blank">python-memcached</a> oraz <a href="https://github.com/lericson/pylibmc" target="_blank">pylibmc</a>. Pierwszy test odbył się standardowo poprzez odpalenie jednego procesu. Drugi natomiast z wykorzystaniem <a href="http://docs.oracle.com/cd/E17952_01/refman-5.0-en/ha-memcached-using-threads.html" target="_blank">czterech procesów</a> co znacznie przyspieszyło działanie.<br />
<br />
<h1>
Jeden proces:</h1>
<br />
<h3>
python-memcached</h3>
<script src="https://gist.github.com/tomislater/6743365.js"></script><br />
<h3>
ultramemcache</h3>
<script src="https://gist.github.com/tomislater/6743373.js"></script><br />
<h3>
pylibmc</h3>
<script src="https://gist.github.com/tomislater/6745756.js"></script><br />
<h1>
Cztery procesy:</h1>
<br />
<h3>
python-memcached</h3>
<script src="https://gist.github.com/tomislater/6744440.js"></script><br />
<h3>
ultramemcache</h3>
<script src="https://gist.github.com/tomislater/6744446.js"></script><br />
<h3>
pylibmc</h3>
<script src="https://gist.github.com/tomislater/6745774.js"></script><br />
<h3>
Wyniki</h3>
<div style="text-align: center;">
<iframe frameborder="0" height="751" scrolling="no" src="//e.infogr.am/JEDEN-PROCES-3998" style="border: none;" width="550"></iframe></div>
Testy wykonałem kilka razy dla każdego ze skryptów i wybrałem najlepsze wyniki dla danej biblioteki.<br />
<br />
Był to bardzo prosty test, ale da się zauważyć spore różnice w szybkości działania tych bibliotek. W przypadku jednego procesu ultramemcache poradził sobie najlepiej, chociaż pylibmc jest tuż tuż... Jeżeli chodzi o cztery procesy to pylibmc jest zdecydowanym <a href="https://www.youtube.com/watch?v=tvbyY7oMT2E" target="_blank">zwycięzcą</a>.<br />
<br />
Zachęcam do robienia swoich testów. Do sporządzenia wykresu skorzystałem z <a href="http://infogr.am/beta/" target="_blank">infogr.am</a>. Jedno z lepszych narzędzi do sporządzania infografik z jakich korzystałem.<br />
<br />
Update: dodałem testy dla <a href="https://github.com/lericson/pylibmc" target="_blank">pylibmc</a>. tomislaterhttp://www.blogger.com/profile/02400052243262571045noreply@blogger.com0