<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>The Ops Community ⚙️: Lucy Linder</title>
    <description>The latest articles on The Ops Community ⚙️ by Lucy Linder (@derlin).</description>
    <link>https://community.ops.io/derlin</link>
    <image>
      <url>https://community.ops.io/images/xikv6rZYU0vdeB3RsXAI6Rw1_U3IU0UHq9P0aH7H4sI/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL3Vz/ZXIvcHJvZmlsZV9p/bWFnZS8yMTIvZWE5/MDEwMTMtZWU1MS00/MDQ2LTkwNjQtNDBh/MzhiYmQ2YTQzLnBu/Zw</url>
      <title>The Ops Community ⚙️: Lucy Linder</title>
      <link>https://community.ops.io/derlin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://community.ops.io/feed/derlin"/>
    <language>en</language>
    <item>
      <title>AWS S3 multipart uploads from unauthenticated users? presigned URLs (😕) vs federation tokens (😃)</title>
      <dc:creator>Lucy Linder</dc:creator>
      <pubDate>Tue, 27 Jun 2023 12:00:00 +0000</pubDate>
      <link>https://community.ops.io/derlin/aws-s3-multipart-uploads-from-unauthenticated-users-presigned-urls-vs-federation-tokens--52hd</link>
      <guid>https://community.ops.io/derlin/aws-s3-multipart-uploads-from-unauthenticated-users-presigned-urls-vs-federation-tokens--52hd</guid>
      <description>&lt;p&gt;I had a very interesting use case lately: being able to upload a file to S3 without being signed in to AWS &lt;em&gt;and&lt;/em&gt; taking advantage of multipart uploads (large files) in Python. This made me dig deeper into AWS &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html"&gt;presigned URLs&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html"&gt;multipart uploads&lt;/a&gt;. And the fun part is, the final solution doesn't use any of it! Curious? Read on!&lt;/p&gt;






&lt;ul&gt;
&lt;li&gt;The use case&lt;/li&gt;
&lt;li&gt;
Attempt 1: REST + presigned URLs 😕

&lt;ul&gt;
&lt;li&gt;A simple PUT with a presigned URL&lt;/li&gt;
&lt;li&gt;Multipart uploads with presigned URLs&lt;/li&gt;
&lt;li&gt;The implementation&lt;/li&gt;
&lt;li&gt;The problems&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Attempt 2: temporary credentials 😃

&lt;ul&gt;
&lt;li&gt;About federation tokens&lt;/li&gt;
&lt;li&gt;(Multipart) uploads with federation tokens&lt;/li&gt;
&lt;li&gt;Are federation tokens safe?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;🔖 I created this Table of Contents using &lt;a href="https://derlin.github.io/bitdowntoc/"&gt;BitDownToc&lt;/a&gt;. If you are curious, read my article: &lt;a href="https://dev.to/derlin/finally-a-clean-and-easy-way-to-add-table-of-contents-to-devto-articles-5g6n"&gt;Finally a clean and easy way to add Table of Contents to dev.to articles 🤩&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The use case
&lt;/h2&gt;

&lt;p&gt;I have an API with its own authentication mechanism that uses AWS S3 as a file storage and provides a CLI to simplify the user experience. Through the CLI, users can do cool stuff such as &lt;em&gt;uploading files&lt;/em&gt; (used later to do other cool stuff, but the details do not matter). In the current implementation, the CLI sends the files (that can be multiple GBs!) to the API (using a &lt;code&gt;POST&lt;/code&gt; 😬), which subsequently handles the upload to S3.&lt;/p&gt;

&lt;p&gt;To make it more efficient, I want the CLI to upload files directly to S3 and leverage AWS multipart uploads. Multipart upload means splitting a large file into chunks that can be uploaded in parallel (faster) and retried separately (more reliable).&lt;/p&gt;

&lt;p&gt;In summary, I need the ability to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;upload a file to S3 without "real" AWS credentials (or at least with limited temporary permissions provided by the API), &lt;em&gt;and&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;use the S3 multipart upload mechanism.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Attempt 1: REST + presigned URLs 😕
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html"&gt;AWS documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use presigned URLs to grant time-limited access to objects in Amazon S3 without updating your bucket policy. [...] The credentials used by the presigned URL are those of the AWS user who generated the URL.&lt;/p&gt;

&lt;p&gt;You can use presigned URLs to allow someone to upload a specific object to your Amazon S3 bucket. This allows an upload without requiring another party to have AWS security credentials or permissions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Presigned URLs for upload contain a bucket, a path, and an expiration date. You can use the link multiple times (it will replace the object) until the expiration.&lt;/p&gt;

&lt;h3&gt;
  
  
  A simple PUT with a presigned URL
&lt;/h3&gt;

&lt;p&gt;To generate a presigned URL for upload with the boto3 s3 client, I can use either &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/generate_presigned_post.html"&gt;generate_presigned_post&lt;/a&gt; or &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/generate_presigned_url.html"&gt;generate_presigned_url&lt;/a&gt;. I prefer the latter, as it returns a single URL ready for use instead of an URL plus some &lt;code&gt;fields&lt;/code&gt; that need to be passed with the &lt;code&gt;PUT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Given the required environment variables (&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, &lt;code&gt;AWS_DEFAULT_REGION&lt;/code&gt;) are present, here is the API side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gen_presigned_url_for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# boto3 will read credentials from the environment
&lt;/span&gt;    &lt;span class="n"&gt;s3_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"s3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ClientMethod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"put_object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the user (CLI) side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;use_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Same as curl -X PUT --upload-file &amp;lt;local-file&amp;gt; &amp;lt;url&amp;gt;
&lt;/span&gt;    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="c1"&gt;# Important: clear headers!
&lt;/span&gt;    &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gen_presigned_url_for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-bucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"foo/data.dump"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;use_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"local.dump"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but a single &lt;code&gt;PUT&lt;/code&gt; is limited to &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/upload-objects.html"&gt;5 GB&lt;/a&gt;! For large files, AWS recommends using multipart uploads, which have many advantages, including improved throughput (parallel upload) and quick recovery from network issues (retry only the failed part).&lt;/p&gt;

&lt;h3&gt;
  
  
  Multipart uploads with presigned URLs
&lt;/h3&gt;

&lt;p&gt;Multipart upload is a 3-steps process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Initialization&lt;/strong&gt; - you tell AWS your intent to upload a file in parts. It returns a unique id (&lt;code&gt;UploadId&lt;/code&gt;) that you need to upload parts and finish/cancel the upload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Part upload&lt;/strong&gt; - with each upload, you pass the &lt;code&gt;UploadId&lt;/code&gt; plus a unique &lt;code&gt;PartNumber&lt;/code&gt; of your choice; AWS returns an &lt;code&gt;ETag&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part numbers&lt;/strong&gt; can be any number from 1 to 10,000, inclusive. A part number uniquely identifies a part and also defines its position within the object being created. If you upload a new part using the same part number, the previously uploaded part is overwritten. The part size should be between 5 MiB to 5 GiB. There is no minimum size limit on the last part of your multipart upload (see &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html"&gt;Multipart upload limit&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Completion&lt;/strong&gt; - you finish the upload by sending to AWS both the &lt;code&gt;UploadId&lt;/code&gt; and the list of (&lt;code&gt;PartNumber&lt;/code&gt; + &lt;code&gt;Etag&lt;/code&gt;) for each part. AWS assembles the parts into a single file (following the &lt;code&gt;PartNumber&lt;/code&gt;s order) and deletes the individual parts.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(&lt;em&gt;Note that parts stay around in S3 until you finish/cancel the upload - that incurs charges! Don't forget to set some cleanup policy for dangling parts if you use this solution.&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;So far, so good. Now, what about presigned URLs? Well, the unauthenticated client only performs part uploads (step 2). However, each part upload has different parameters (thanks to the &lt;code&gt;PartId&lt;/code&gt;), hence requiring a different presigned URL. In other words, you need &lt;strong&gt;as many presigned URLs as the client will have parts&lt;/strong&gt; to upload!&lt;/p&gt;

&lt;p&gt;This process is discussed in the boto3 issue entitled &lt;a href="https://github.com/boto/boto3/issues/2305#issuecomment-591128376"&gt;How to use Pre-signed URLs for multipart upload&lt;/a&gt;. To make it clearer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.plantuml.com/plantuml/uml/RP1VQy8m5CNVyockRugsuAiCSPJ9P0FBh4mGaTZUrM2QXFpPllwIJBj5rgScEUV-pZqBOwcshkDaOC_O8NooawR24kMlMSsK_mTdK0CrK11IPDdyiLp1U3o35No5Lol1AIvf0nG-64TW0dPGdvsu6EArmV6-YOvQ8xLux1otB02EFyRXoTKNj9De7n6llbAoR0RRA5arX4kfO2ari42OvlbkF2NWlvJHea0TA4gZOQt0vkWq7zx6J82DeYkbz6BOlhqI5CqrUH5VzyaiSo1vdl_cSI5FgeY4ua-gvwegeLnlAgz-2AlFRo5wIEblGxwdYhOXGO0aVI-POEQCpG3smrviQJiwtDSiEyNnlwBzoFj-ro_9gk6yy7JNyMy0"&gt;&lt;img src="https://community.ops.io/images/QTkhuyLJkS2gsPk43adzO7ZZnWFKbxNvCA9FkUx1qjE/w:800/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2hzdmlh/enYwb2psZzUwb3Ry/eDZqLnBuZw" alt="PlantUML diagram" width="584" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The implementation
&lt;/h3&gt;

&lt;p&gt;How does this translate in Python code? First, the API side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeAPIWithAWSAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# ↓ AWS client. Requires environment variables!
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"s3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_multipart_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"API: starting multipart for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;num_parts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; URLs."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Initialize the multipart upload
&lt;/span&gt;        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_multipart_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;upload_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"UploadId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Generate the presigned URL for each part
&lt;/span&gt;        &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part_number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_parts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# parts start at 1
&lt;/span&gt;            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="c1"&gt;# The s3 operation is "upload_part"
&lt;/span&gt;                &lt;span class="n"&gt;ClientMethod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"upload_part"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"Bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"UploadId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;upload_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"PartNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;part_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;part_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="c1"&gt;# Create a callback that can be called when
&lt;/span&gt;        &lt;span class="c1"&gt;# the upload is finished on the user side
&lt;/span&gt;        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;finish_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"API: finishing multipart upload."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;complete_multipart_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;MultipartUpload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;UploadId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;upload_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Return the URLs and the callback
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finish_callback&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now the user (CLI) side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ceil&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-bucket"&lt;/span&gt; &lt;span class="c1"&gt;# CHANGE_ME: bucket name
&lt;/span&gt;&lt;span class="n"&gt;remote_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"foo/data.dump"&lt;/span&gt; &lt;span class="c1"&gt;# CHANGE_ME: path in the bucket
&lt;/span&gt;&lt;span class="n"&gt;local_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data.dump"&lt;/span&gt; &lt;span class="c1"&gt;# CHANGE_ME: file to upload
&lt;/span&gt;
&lt;span class="n"&gt;chunk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;  &lt;span class="c1"&gt;# 5 MB (minimal part size)
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_num_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: make part sizes even
&lt;/span&gt;    &lt;span class="n"&gt;filesize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filesize&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;num_parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_num_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Start a multipart upload
&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"asking for presigned URLs."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SomeAPIWithAWSAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i_am_done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_multipart_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;remote_location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Upload each part
&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"uploading part &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;part_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# we have to append etag and partnumber of each parts
&lt;/span&gt;        &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;"ETag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ETag"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"PartNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;part_number&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"calling finish."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;i_am_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test it, create a random large file of 12MB, export the AWS credentials, and call the program. Don't forget to change the bucket name in the code above!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AWS&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-1

&lt;span class="c"&gt;# Create a "large" file to upload&lt;/span&gt;
&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/urandom &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;data.dump &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12m &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="c"&gt;# Call the program&lt;/span&gt;
python aws-multipart-upload.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The problems
&lt;/h3&gt;

&lt;p&gt;I am now able to do multipart uploads with presigned URLs! However:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;the user needs to know in advance the number of parts and understand how multipart uploads work (at least &lt;code&gt;Etag&lt;/code&gt; and &lt;code&gt;PartNumber&lt;/code&gt;),&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the user receives URLs... That can't be used with the boto3 client! It is thus his responsibility to implement parallel uploads, retries, etc. The work is huge!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;using multipart uploads is very inefficient for small files (&amp;lt; 5MB).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In other words, using multipart uploads with presigned URLs doesn't bring any advantages, except if you are willing to spend days implementing your own upload logic on the user side...&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt 2: temporary credentials 😃
&lt;/h2&gt;

&lt;p&gt;What I would like is to be able to use boto3 features on the user side (to get parallel uploads and retries for free) without giving him any permission other than uploading a specific file to a specific s3 bucket location.&lt;/p&gt;

&lt;p&gt;How can I do that? Enter federation tokens!&lt;/p&gt;

&lt;h3&gt;
  
  
  About federation tokens
&lt;/h3&gt;

&lt;p&gt;As explained at length in the docs &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html"&gt;Comparing the AWS STS API operations&lt;/a&gt;&lt;strong&gt;,&lt;/strong&gt; AWS &lt;em&gt;Security Token Service&lt;/em&gt; (STS) provides multiple ways of creating temporary credentials: assuming roles, session tokens, and federation tokens. For my use case, federation tokens are perfect, as they support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;credentials lifetime (i.e. expiry), and&lt;/li&gt;
&lt;li&gt;custom inline policies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Custom inline policies mean I can create a throw-away policy on the fly and attach it to the federation token upon creation. Here is an example that only allows file uploads to path &lt;code&gt;{PATH}&lt;/code&gt; in bucket &lt;code&gt;{BUCKET}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AllowFileUpload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::{FILE}/{BUCKET}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;"AllowFileUpload"&lt;/code&gt; encompasses both regular and multipart uploads.&lt;/p&gt;

&lt;h3&gt;
  
  
  (Multipart) uploads with federation tokens
&lt;/h3&gt;

&lt;p&gt;Using federation tokens is so easy I don't even need a UML diagram this time 😉. From the API point of view, I just have to call STS' &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sts/client/get_federation_token.html"&gt;&lt;code&gt;get_federation_token&lt;/code&gt; endpoint&lt;/a&gt; with the right parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid1&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;EXPIRE_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="c1"&gt;# 1h validity
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeAPIWithAWSAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sts"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_federated_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"upload-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid1&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"services-api-local-testing"&lt;/span&gt;
        &lt;span class="c1"&gt;# The magic is here: the policy only allows file
&lt;/span&gt;        &lt;span class="c1"&gt;# uploads to bucket/key. 
&lt;/span&gt;        &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""{
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "AllowFileUpload",
                    "Effect": "Allow",
                    "Action": "s3:PutObject",
                    "Resource": "arn:aws:s3:::{}/{}"
                }
            ]
        }"""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Get the federation token
&lt;/span&gt;        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_federation_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;DurationSeconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EXPIRE_SECONDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Return only the relevant information
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"access_key_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Credentials"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"AccessKeyId"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s"&gt;"access_key_secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Credentials"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"SecretAccessKey"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s"&gt;"session_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Credentials"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"SessionToken"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s"&gt;"expiration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Credentials"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"Expiration"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s"&gt;"bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user (CLI) side can thus use boto3 to upload files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-bucket"&lt;/span&gt; &lt;span class="c1"&gt;# CHANGE_ME: bucket name
&lt;/span&gt;&lt;span class="n"&gt;remote_location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"foo/data.dump"&lt;/span&gt; &lt;span class="c1"&gt;# CHANGE_ME: path in the bucket
&lt;/span&gt;&lt;span class="n"&gt;local_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data.dump"&lt;/span&gt; &lt;span class="c1"&gt;# CHANGE_ME: file to upload
&lt;/span&gt;
&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SomeAPIWithAWSAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_federated_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remote_location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"s3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# Use the creds to login to AWS
&lt;/span&gt;    &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"access_key_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"access_key_secret"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;aws_session_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"session_token"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# Upload the file
&lt;/span&gt;    &lt;span class="n"&gt;local_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"bucket"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Way easier. But what about multipart uploads? The beauty of this solution is that the boto3 s3 client takes care of everything. We can see this by looking at the &lt;code&gt;upload_file&lt;/code&gt; &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/s3.html#boto3.s3.transfer.TransferConfig"&gt;TransferConfig&lt;/a&gt; options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Default options used by upload_file
&lt;/span&gt;&lt;span class="n"&gt;TransferConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# Automatically use multipart uploads for files &amp;gt;= 8M
&lt;/span&gt;    &lt;span class="n"&gt;multipart_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8388608&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# Do uploads in parallel
&lt;/span&gt;    &lt;span class="n"&gt;use_threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# Use at most 10 threads
&lt;/span&gt;    &lt;span class="n"&gt;max_concurrency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# Other transfer options
&lt;/span&gt;    &lt;span class="n"&gt;multipart_chunksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8388608&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_download_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_io_queue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;io_chunksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;262144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_bandwidth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And of course, AWS clients exist for other programming languages, so a user that does not use the CLI is not stuck with Python 😊.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are federation tokens safe?
&lt;/h3&gt;

&lt;p&gt;Contrary to a presigned URL, a user can log in to the AWS console with an access + secret key. However, since I attached a very restrictive policy to the federation token, it won't let him do or see anything (except menus). In other words, as long as the policy is sane, there is no security risk involved in returning a federation token to an untrusted user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at AWS presigned URLs, and how to make them work with multipart uploads. This is however complex and fails to deliver the desired advantages: parallel uploads and separate retries need to be coded on the client side.&lt;/p&gt;

&lt;p&gt;We then looked at federation tokens, and how they make the whole process easier: the user can upload files to S3 using the AWS client, which takes care of all the heavy lifting. Moreover, the federation token has very limited permissions and expires after a while, making it as secure as presigned URLs.&lt;/p&gt;

&lt;p&gt;With love, &lt;a href="https://derlin.ch"&gt;@derlin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>secops</category>
      <category>cloudops</category>
    </item>
    <item>
      <title>Installing HashiCorp Vault + ExternalSecrets Operator on Kubernetes: the easy way</title>
      <dc:creator>Lucy Linder</dc:creator>
      <pubDate>Wed, 08 Mar 2023 11:33:07 +0000</pubDate>
      <link>https://community.ops.io/derlin/installing-hashicorp-vault-externalsecrets-operator-on-kubernetes-the-easy-way-2ci5</link>
      <guid>https://community.ops.io/derlin/installing-hashicorp-vault-externalsecrets-operator-on-kubernetes-the-easy-way-2ci5</guid>
      <description>&lt;p&gt;&lt;em&gt;Want to play with Vault and ExternalSecrets, but don't want to spend a day setting them up? Here is the perfect repo for you.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recently had to test the new &lt;a href="https://external-secrets.io/main"&gt;ExternalSecrets operator&lt;/a&gt; and its capabilities when using &lt;a href="http://vaultproject.io/"&gt;HashiCorp Vault&lt;/a&gt; as a backend. I spent some time figuring out how to install them on a local &lt;a href="https://k3d.io"&gt;K3D&lt;/a&gt; cluster and wanted to share it so you won't have to.&lt;/p&gt;

&lt;p&gt;⮕ ✨✨ &lt;a href="https://github.com/derlin/externalsecrets-with-hashicorp-vault-kubernetes-easy-install"&gt;https://github.com/derlin/externalsecrets-with-hashicorp-vault-kubernetes-easy-install&lt;/a&gt; ✨✨&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;: this is for test purposes only, it is not suitable for production!&lt;/p&gt;






&lt;ul&gt;
&lt;li&gt;About Vault and ExternalSecrets&lt;/li&gt;
&lt;li&gt;
Installing Vault and ExternalSecrets

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Procedure&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Accessing the vault
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About Vault and ExternalSecrets
&lt;/h2&gt;

&lt;p&gt;Kubernetes' &lt;code&gt;Secrets&lt;/code&gt; resources are a way to store sensitive information. Those Secret resources may be created directly (YAML files), from a Helm Chart, from Kustomize, etc. If you follow a &lt;em&gt;gitops approach&lt;/em&gt; (you should!) those YAML files, Helm Charts, etc. will live in a git repo somewhere. But you don't want to commit sensitive information in git repos, so how to proceed?&lt;/p&gt;

&lt;p&gt;A good approach instead is to use a secret management system (there are plenty to choose from: &lt;a href="https://aws.amazon.com/secrets-manager/"&gt;AWS Secrets Manager&lt;/a&gt;, &lt;a href="https://www.vaultproject.io/"&gt;HashiCorp Vault&lt;/a&gt;, &lt;a href="https://cloud.google.com/secret-manager"&gt;Google Secrets Manager&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/services/key-vault/"&gt;Azure Key Vault&lt;/a&gt;, &lt;a href="https://www.ibm.com/cloud/secrets-manager"&gt;IBM Cloud Secrets Manager&lt;/a&gt;, etc.), and to have a way to retrieve those secrets dynamically from your Kubernetes cluster. This is where ExternalSecrets shines.&lt;/p&gt;

&lt;p&gt;ExternalSecrets is a cluster-wide operator that you install once. Then, instead of creating a &lt;code&gt;Secret&lt;/code&gt; directly, you create an &lt;code&gt;ExternalSecret&lt;/code&gt; (a custom resource) that defines what secrets to retrieve, and from which backend. The operator then creates the &lt;code&gt;Secret&lt;/code&gt; for you.&lt;/p&gt;

&lt;p&gt;Backends are configured by creating &lt;code&gt;SecretStore&lt;/code&gt; or &lt;code&gt;ClusterSecretStore&lt;/code&gt; resources, which hold the connection information to a given secret management system. Each &lt;code&gt;ExternalSecret&lt;/code&gt; must reference one of those secret stores, so the operator knows from which backend it should retrieve secrets.&lt;/p&gt;

&lt;p&gt;The documentation at &lt;a href="https://external-secrets.io/main/"&gt;https://external-secrets.io/main/&lt;/a&gt; is quite good, so I will stop here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Vault and ExternalSecrets
&lt;/h2&gt;

&lt;p&gt;To simplify the installation, I use helmfile, which uses helm under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Hard requirements&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://helm.sh"&gt;helm&lt;/a&gt; (&lt;code&gt;brew install helm&lt;/code&gt;) &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://helmfile.readthedocs.io/"&gt;helmfile&lt;/a&gt; (&lt;code&gt;brew install helmfile&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Soft requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/databus23/helm-diff"&gt;helm diff plugin&lt;/a&gt; (&lt;code&gt;helm plugin install https://github.com/databus23/helm-diff&lt;/code&gt;). This is necessary if you plan to use &lt;code&gt;helmfile apply&lt;/code&gt; and &lt;code&gt;helmfile diff&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="//k3d.io/"&gt;k3d&lt;/a&gt; to be able to spawn a local Kubernetes cluster on Docker (&lt;code&gt;brew install k3d&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Procedure
&lt;/h3&gt;

&lt;p&gt;First, clone the following repo: &lt;a href="https://github.com/derlin/externalsecrets-with-hashicorp-vault-kubernetes-easy-install"&gt;https://github.com/derlin/externalsecrets-with-hashicorp-vault-kubernetes-easy-install&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start a k3d cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k3d cluster create &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--api-port&lt;/span&gt; 6550 &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"80:80@loadbalancer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Vault, the ExternalSecret operator, and a &lt;code&gt;ClusterSecretStore&lt;/code&gt; by running the following at the root of the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helmfile &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Launch a Vault instance in the &lt;code&gt;vault&lt;/code&gt; namespace, and configure it with a token &lt;code&gt;root&lt;/code&gt;,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install the ExternalSecrets operator in the &lt;code&gt;es&lt;/code&gt; namespace,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;ClusterSecretStore&lt;/code&gt; resource named &lt;code&gt;vault-backend&lt;/code&gt; in the &lt;code&gt;default&lt;/code&gt; namespace, which connects to the Vault. You can reference it in an ExternalSecret resource using:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExternalSecret&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;secretStoreRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-backend&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterSecretStore&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a secret in the vault under the path &lt;code&gt;secret/foo&lt;/code&gt; with one property, &lt;code&gt;hello&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done! Now, you can test the operator by creating an &lt;code&gt;ExternalSecret&lt;/code&gt; resource and wait for the &lt;code&gt;Secret&lt;/code&gt; &lt;code&gt;test&lt;/code&gt; to be created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; extsecret-example.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessing the vault
&lt;/h2&gt;

&lt;p&gt;The setting above automatically creates the &lt;code&gt;secret/foo&lt;/code&gt; for you. To &lt;strong&gt;access the vault interface&lt;/strong&gt; and add more secrets, create a port forward to access the vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward &lt;span class="nt"&gt;-n&lt;/span&gt; vault vault-0 8200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now go to &lt;a href="http://localhost:8200"&gt;http://localhost:8200&lt;/a&gt; and log in with the default token &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To access the vault &lt;strong&gt;using the command line&lt;/strong&gt; (and assuming the port-forwarding is still on):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VAULT_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:8200
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;root

vault kv get secret/foo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also set secrets programmatically using &lt;code&gt;kubectl exec&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;vault-0 &lt;span class="nt"&gt;-n&lt;/span&gt; vault &lt;span class="nt"&gt;--&lt;/span&gt; vault kv put secret/foo app-secret-key&lt;span class="o"&gt;=&lt;/span&gt;123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>secops</category>
    </item>
    <item>
      <title>one Docker image to rule them all</title>
      <dc:creator>Lucy Linder</dc:creator>
      <pubDate>Mon, 27 Jun 2022 12:21:34 +0000</pubDate>
      <link>https://community.ops.io/derlin/one-docker-image-to-rule-them-all-8j1</link>
      <guid>https://community.ops.io/derlin/one-docker-image-to-rule-them-all-8j1</guid>
      <description>&lt;p&gt;I just found out &lt;a href="https://nixery.dev"&gt;nixery&lt;/a&gt; ! &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Nixery is a Docker-compatible container registry that is capable of transparently building and serving container images using Nix.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Images are &lt;strong&gt;built on-demand&lt;/strong&gt; based on the image name. Every package that the user intends to include in the image is specified as a path component of the image name.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The path components refer to top-level keys in nixpkgs and are used to build a container image using a layering strategy that optimises for caching popular and/or large dependencies.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, you start with the base image, &lt;code&gt;nixery.dev/&lt;/code&gt;, and then lists the packages and tools you want available. Usually, you start with the &lt;code&gt;shell&lt;/code&gt; metapackage, followed by any &lt;a href="https://search.nixos.org/packages?channel=22.05&amp;amp;show=pingtcp&amp;amp;from=0&amp;amp;size=50&amp;amp;sort=relevance&amp;amp;type=packages"&gt;NixOS package(s)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is very handy when working with Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;
  Command format to run an ephemeral pod on Kubernetes
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nixery.dv/&amp;lt;PACKAGES&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &amp;lt;NAME&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; &amp;lt;CMD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;Connect to a database using &lt;code&gt;psql&lt;/code&gt;, assuming the service is called &lt;code&gt;my-db&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nixery.dev/postgresql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;some-password &lt;span class="se"&gt;\&lt;/span&gt;
   psql &lt;span class="nt"&gt;--&lt;/span&gt; psql &lt;span class="nt"&gt;-h&lt;/span&gt; my-db &lt;span class="nt"&gt;-U&lt;/span&gt; some-username
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the connectivity to a pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nixery.dev/shell/unixtools.ping &lt;span class="se"&gt;\&lt;/span&gt;
  ping &lt;span class="nt"&gt;--&lt;/span&gt; ping keycloak.cluster.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get a shell with &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;nc&lt;/code&gt; commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nixery.dev/shell/curl/gnugrep/ping/netcat &lt;span class="se"&gt;\&lt;/span&gt;
  shell &lt;span class="nt"&gt;--&lt;/span&gt; bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;For those not familiar with NixOs, it may be troublesome to find the package name that will bring you the executable you need. Here are some:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;psql&lt;/code&gt; → package &lt;code&gt;postgresql&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ping&lt;/code&gt; → package &lt;code&gt;unixtools.ping&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grep&lt;/code&gt; → package &lt;code&gt;gnugrep&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nc&lt;/code&gt; → package &lt;code&gt;netcat&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, I wasn't able to run with &lt;code&gt;root&lt;/code&gt; permissions, meaning I could not run &lt;code&gt;iptables -L&lt;/code&gt; (with the package &lt;code&gt;iptables&lt;/code&gt;). Maybe I missed something ? Let me know in the comments !&lt;/p&gt;

</description>
      <category>devops</category>
      <category>productivity</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>helmfile: a simple trick to handle values intuitively</title>
      <dc:creator>Lucy Linder</dc:creator>
      <pubDate>Mon, 20 Jun 2022 15:18:30 +0000</pubDate>
      <link>https://community.ops.io/derlin/helmfile-a-simple-trick-to-handle-values-intuitively-50cb</link>
      <guid>https://community.ops.io/derlin/helmfile-a-simple-trick-to-handle-values-intuitively-50cb</guid>
      <description>&lt;p&gt;&lt;a href="https://helmfile.readthedocs.io"&gt;helmfile&lt;/a&gt; is a very nice and powerful tool to manage multiple Helm charts declaratively. However, there is one area in which I find it suboptimal: the handling of values / environment values.&lt;/p&gt;

&lt;p&gt;Let's go over how it works, and see how we can make it better. If you don't like to read, skip to My tip on using values in helmfile (or read the TL;DR in the repo linked below).&lt;/p&gt;





&lt;center&gt;
&lt;br&gt;
&lt;strong&gt;For a full example, check out this code !&lt;/strong&gt;&lt;br&gt;
👉 ✨ &lt;strong&gt;&lt;a href="https://github.com/derlin/helmfile-intuitive-values-handling"&gt;https://github.com/derlin/helmfile-intuitive-values-handling&lt;/a&gt;&lt;/strong&gt; ✨ 👈&lt;br&gt;
&lt;/center&gt;


&lt;h2&gt;
  
  
  Values in umbrella charts (pure Helm)
&lt;/h2&gt;

&lt;p&gt;Coming from the Helm world, I am used to using umbrella charts, where all the default values for my charts are defined in one single &lt;code&gt;values.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# globals are available to all sub-charts &lt;/span&gt;
&lt;span class="c1"&gt;# using .Values.global.*&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.example.com&lt;/span&gt;

&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# default values passed to the sub-chart called 'foo'&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

&lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# default values passsed to the sub-chart called 'bar'&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When some values need to be overridden per environment, I simply create a file &lt;code&gt;&amp;lt;env&amp;gt;.yaml&lt;/code&gt; and pass it to helm using &lt;code&gt;--values&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# in environments/prod.yaml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod.example.com&lt;/span&gt; &lt;span class="c1"&gt;# override the domain for all&lt;/span&gt;

&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.19&lt;/span&gt; &lt;span class="c1"&gt;# use a stable docker image &lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To deploy to prod:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;my-umbrella-name &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--values&lt;/span&gt; environments/prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With helmfile though, there is no easy way to reproduce this behavior (well, there is actually, keep reading 😉).&lt;/p&gt;
&lt;h2&gt;
  
  
  Values in helmfile
&lt;/h2&gt;

&lt;p&gt;In helmfile, one defines default values for a chart using the &lt;code&gt;releases.&amp;lt;name&amp;gt;.values&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;releases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
          &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To add global values, there is an equivalent &lt;code&gt;environments.default.values&lt;/code&gt;, but this only makes values available to the templates... It doesn't attach those values automatically. In other words, the following does nothing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;releases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;environments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To make it work, we need to add some values template to release &lt;code&gt;foo&lt;/code&gt; (and all other releases), for example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;releases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;toYaml .Values | nindent 8&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, &lt;code&gt;prod: true&lt;/code&gt; will be passed to &lt;code&gt;foo&lt;/code&gt; upon &lt;code&gt;helmfile -e prod&lt;/code&gt; ...&lt;/p&gt;

&lt;p&gt;The environment values are passed to all release &lt;em&gt;templates&lt;/em&gt;, not releases! That is, they can be used inside &lt;code&gt;gotmpl&lt;/code&gt; templates/files listed under &lt;code&gt;release.&amp;lt;name&amp;gt;.values&lt;/code&gt;, but are not attached directly ...&lt;/p&gt;

&lt;p&gt;This is already too complex to follow.&lt;/p&gt;
&lt;h2&gt;
  
  
  My tip on using values in helmfile
&lt;/h2&gt;

&lt;p&gt;Instead of trying to understand how all those values work (and creating specific &lt;code&gt;.gotmpl&lt;/code&gt; files for each release), here is how I managed to &lt;strong&gt;mimic the umbrella chart behavior&lt;/strong&gt; regarding values with helmfile (one default value file + one file per environment, with &lt;code&gt;global&lt;/code&gt; section and &lt;code&gt;&amp;lt;release-name&amp;gt;&lt;/code&gt; sections).&lt;/p&gt;

&lt;p&gt;First, create a folder called &lt;code&gt;environments&lt;/code&gt;. In it, create a &lt;code&gt;default.yaml&lt;/code&gt; file, and specify the default values for each release and the globals using the "umbrella chart syntax":&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ... values passed to all releases&lt;/span&gt;
&lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ... values passed to release foo&lt;/span&gt;
&lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ... values passed to release bar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, create as many files as you have environments (environment &lt;code&gt;prod&lt;/code&gt; → &lt;code&gt;environments/prod.yaml&lt;/code&gt;) and override only what needs to be overridden (compared to default). &lt;/p&gt;

&lt;p&gt;In the helmfile, configure each environment to read from &lt;code&gt;default.yaml&lt;/code&gt; and the specific environment values:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# in helmfile.yaml&lt;/span&gt;
&lt;span class="na"&gt;environments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;environments/default.yaml&lt;/span&gt;
  &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# apply default first, then prod&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;environments/default.yaml&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;environments/prod.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, here is the trick.&lt;br&gt;
Create a &lt;em&gt;magic&lt;/em&gt; &lt;code&gt;gotmpl&lt;/code&gt; file that will extract both the &lt;code&gt;global&lt;/code&gt; section and the release-specific section of the values:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="c"&gt;/* in env-magic.gotmpl */&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="c"&gt;/* 
extract both global and &amp;lt;release-name&amp;gt; sections from
.Values, and merge them (giving precedence to release
specific values.
Note: missing entries are fine.
*/&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;  &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s"&gt;"global"&lt;/span&gt;  &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;toYaml&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And attach this magic file to all releases in the helmfile:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# in helmfile.yaml&lt;/span&gt;
&lt;span class="na"&gt;releases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;env&lt;/span&gt; &lt;span class="s"&gt;env-magic.gotmpl&lt;/span&gt; &lt;span class="c1"&gt;# use a YAML anchor for DRYness&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bar&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;*env&lt;/span&gt; &lt;span class="c1"&gt;# reference the anchor&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it! Now, you can simply edit the files in &lt;code&gt;environments/&lt;/code&gt;, and don't have to think about (or touch) values in helmfile anymore.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;A complete example (and a different explanation) is available here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/derlin"&gt;
        derlin
      &lt;/a&gt; / &lt;a href="https://github.com/derlin/helmfile-intuitive-values-handling"&gt;
        helmfile-intuitive-values-handling
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      How to manage values (globals, environment, release-specific) intuitively within helmfile
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
How to handle helmfile values nicely&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/helmfile/helmfile"&gt;helmfile&lt;/a&gt; is a very powerful tool, but his way of handling release values is daunting for the beginners (and experts). I propose here a simple pattern to handle values, that is completely generic, intuitive and works in all situations.&lt;/p&gt;
&lt;p&gt;
 &lt;b&gt;Read the article ! &lt;/b&gt;&lt;br&gt;
 &lt;a href="https://community.ops.io/derlin/helmfile-a-simple-trick-to-handle-values-intuitively-50cb" rel="nofollow"&gt;&lt;b&gt; 👉 ✨ helmfile: a simple trick to handle values intuitively ✨ 👈&lt;/b&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;h2&gt;
TL;DR&lt;/h2&gt;
&lt;p&gt;This repo reproduces the way values are handled in umbrella charts (Helm Charts with sub-charts).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you don't like to read&lt;/strong&gt; but want to experiment instead:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clone this repo,&lt;/li&gt;
&lt;li&gt;customize the different release values by editing &lt;code&gt;environments/default.yaml&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;override values per environment by editing &lt;code&gt;environments/&amp;lt;envName&amp;gt;.yaml&lt;/code&gt; (available environements are &lt;code&gt;local&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;see for yourself how your changes work by running: &lt;code&gt;helmfile -e &amp;lt;env&amp;gt; write-values&lt;/code&gt; and see the output.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To apply this in your helmfile:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ensure you reference &lt;code&gt;env-magic.gotmpl&lt;/code&gt; under &lt;em&gt;all&lt;/em&gt; release values (&lt;code&gt;releases.&amp;lt;releaseName&amp;gt;.values&lt;/code&gt;) and,&lt;/li&gt;
&lt;li&gt;ensure…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/derlin/helmfile-intuitive-values-handling"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;






&lt;p&gt;Written with ❤ by &lt;a href="https://derlin.ch"&gt;derlin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>helmfile</category>
    </item>
    <item>
      <title>Helm templates: do not use tpl in vain</title>
      <dc:creator>Lucy Linder</dc:creator>
      <pubDate>Mon, 30 May 2022 17:56:36 +0000</pubDate>
      <link>https://community.ops.io/derlin/helm-templates-do-not-use-tpl-in-vain-g12</link>
      <guid>https://community.ops.io/derlin/helm-templates-do-not-use-tpl-in-vain-g12</guid>
      <description>&lt;p&gt;From the documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The &lt;code&gt;tpl&lt;/code&gt; function allows developers to evaluate strings as templates inside a template. This is useful to pass a template string as a value to a chart or render external configuration files. Syntax: &lt;code&gt;{{ tpl TEMPLATE_STRING VALUES }}&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When writing generic Helm charts or libraries, &lt;strong&gt;calls to &lt;code&gt;tpl&lt;/code&gt; are often overused, and for a good reason&lt;/strong&gt;: they give lots of flexibility, and allow chart users to avoid repetition in the &lt;code&gt;values.yaml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tpl&lt;/code&gt; function is however costly, and slow, and this is why it should be called only when necessary. &lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/helm/helm/issues/8002"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        `tpl` function not performant
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#8002&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/himmakam"&gt;
        &lt;img class="github-liquid-tag-img" src="https://community.ops.io/images/u-R4bTkTSYCupxqvLxMBsrw76324Axq6118_FEYWUAY/w:880/mb:500000/ar:1/aHR0cHM6Ly9hdmF0/YXJzLmdpdGh1YnVz/ZXJjb250ZW50LmNv/bS91LzM4MTI5MDU0/P3Y9NA" alt="himmakam avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/himmakam"&gt;himmakam&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/helm/helm/issues/8002"&gt;&lt;time&gt;Apr 27, 2020&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;I have a scenario of having one umbrella chart with many sub-charts as dependencies. When I have the templates folder in umbrella chart, helm lint takes more time like almost 30 to 40 minutes. When this folder is not there, helm lint returns very fast. Can I know the reason.running in debug mode not giving any logs. It is helm v3.&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/helm/helm/issues/8002"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;How to limit calls while keeping the flexibility ?&lt;/strong&gt; My solution is to wrap all calls to &lt;code&gt;tpl&lt;/code&gt; using the following helper function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;define&lt;/span&gt; &lt;span class="s"&gt;"bettertpl"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="c"&gt;/* handle cases where .value is a yaml object */&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typeIs&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;toYaml&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="c"&gt;/* only call tpl if there is at least one template expression */&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;contains&lt;/span&gt; &lt;span class="s"&gt;"{{"&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the code should make it clear, &lt;code&gt;bettertpl&lt;/code&gt; adds two features to the regular &lt;code&gt;tpl&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;it allows to template anything (not just string), as &lt;code&gt;dict&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, etc. will be converted to string first, and&lt;/li&gt;
&lt;li&gt;it only calls the slow &lt;code&gt;tpl&lt;/code&gt; when needed: if the string doesn't contain at least one &lt;code&gt;{{ ... }}&lt;/code&gt;, we know we can just print it as is.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;tpl&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="s"&gt;"bettertpl"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt; &lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="s"&gt;"context"&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;
  You find it too verbose ?
  &lt;p&gt;Note that I use named arguments for better readability. You can get rid of them and use lists instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="s"&gt;"bettertpl"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the template above, change &lt;code&gt;.value&lt;/code&gt; → &lt;code&gt;first .&lt;/code&gt; and &lt;code&gt;.context&lt;/code&gt; → &lt;code&gt;index . 2&lt;/code&gt; to read from list arguments instead.&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Is it worth it ?&lt;/strong&gt; As an example, I recently migrated a gitops repository with an umbrella chart of around 20 sub-charts. After a refactoring allowing me to use only one base chart for all, my &lt;code&gt;helm template&lt;/code&gt; went from &amp;lt;1s to more than 15 seconds... &lt;/p&gt;

&lt;p&gt;I was able to take it down to 4 seconds by limiting the calls to &lt;code&gt;tpl&lt;/code&gt; with this simple trick, which is quite an improvement.&lt;/p&gt;

</description>
      <category>helm</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>kubectl run: spawn temporary docker containers on Kubernetes</title>
      <dc:creator>Lucy Linder</dc:creator>
      <pubDate>Thu, 26 May 2022 15:02:43 +0000</pubDate>
      <link>https://community.ops.io/derlin/kubectl-run-spawn-temporary-docker-containers-on-kubernetes-2c3e</link>
      <guid>https://community.ops.io/derlin/kubectl-run-spawn-temporary-docker-containers-on-kubernetes-2c3e</guid>
      <description>&lt;p&gt;When working with Kubernetes, there are times when you wished you had a specific tool to help debug a problem, visualise some data, or take some actions. Well, there is actually an easy way: deploy a docker container with the necessary tool directly to your cluster with one command, use it, and let it be destroyed as soon as you do not use it anymore !&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;kubectl&lt;/code&gt; run command
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#run"&gt;&lt;code&gt;run&lt;/code&gt;&lt;/a&gt; command creates and runs a particular image in a pod. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;run will start running 1 or more instances of a container image on your cluster&lt;/em&gt;&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run NAME &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"key=value"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;port]
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server|client] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--overrides&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;inline-json]
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--command&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;COMMAND] &lt;span class="o"&gt;[&lt;/span&gt;args...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;The image can be anything, as long as it can be pulled from the cluster. With the two options &lt;code&gt;--attach&lt;/code&gt;/&lt;code&gt;-it&lt;/code&gt; (wait for the Pod to start running, and then attach to the Pod / open a shell) and &lt;code&gt;--rm&lt;/code&gt; (delete the pod after it exits), it is the perfect way to get the right tools into the cluster for a short while.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: interact with a database using psql
&lt;/h2&gt;

&lt;p&gt;Let's say your project runs in the namespace &lt;code&gt;myproject&lt;/code&gt; and the micro-services use postgres on RDS (managed relational database on AWS). RDS doesn't come with a nice postgres admin tool, and your infra colleagues only gave you &lt;a href="https://www.adminer.org/"&gt;adminer&lt;/a&gt; to interact with it. You want &lt;code&gt;psql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From your terminal, ensure you are connected to the right kubernetes (&lt;code&gt;kubectl config current-context&lt;/code&gt;) context and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run bash in a container with psql installed &lt;/span&gt;
&lt;span class="c"&gt;# on your namespace&lt;/span&gt;
kubectl &lt;span class="nt"&gt;--namespace&lt;/span&gt; myproject &lt;span class="se"&gt;\&lt;/span&gt;
  run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; psql &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres:13 &lt;span class="nt"&gt;--&lt;/span&gt; bash&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command, you suddenly have a shell in your cluster with &lt;code&gt;psql&lt;/code&gt; installed. You can now run the following to have the &lt;code&gt;psql&lt;/code&gt; prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'my-user-pwd'&lt;/span&gt; psql postgres &lt;span class="nt"&gt;-U&lt;/span&gt; my-user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-h&lt;/span&gt; hostname-of-the-db-cluster.rds.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you have network policies in place, you can easily add the needed labels to your &lt;code&gt;psql&lt;/code&gt; pod using the &lt;code&gt;-l&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run ... &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"db-access: true"&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"role: alice"&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you exit the shell, the pod should disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Kafka UI
&lt;/h2&gt;

&lt;p&gt;Let's say you have a Kafka cluster running, and you need to debug the messages going through. There is no Kafka visual interface on your cluster.&lt;/p&gt;

&lt;p&gt;
  Why not just a port forwarding ?
  &lt;br&gt;
Why not just set a port forwarding (&lt;code&gt;kubectl port-forward my-kafka 9092&lt;/code&gt;) and run some tool locally you ask ? Well, the tool would be able to connect, but the advertised hostname will point to a host only available from within kubernetes. Getting messages will thus fail (unless manually editing &lt;code&gt;/etc/hosts&lt;/code&gt;, which is not possible if the tool runs on a Docker container). &lt;br&gt;


&lt;/p&gt;

&lt;p&gt;Kafka visualisation tools are a plethora: &lt;a href="https://github.com/provectus/kafka-ui"&gt;Kafka UI&lt;/a&gt;, &lt;a href="https://www.kafkamagic.com/"&gt;Kafka Magic&lt;/a&gt;, &lt;a href="https://github.com/obsidiandynamics/kafdrop"&gt;kafdrop&lt;/a&gt; to cite a few.&lt;/p&gt;

&lt;p&gt;Let's take kafka-ui as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--n&lt;/span&gt; myproject run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; kui &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;provectuslabs/kafka-ui:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="s2"&gt;"KAFKA_CLUSTERS_0_NAME=main"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="s2"&gt;"KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS&lt;/code&gt; must match the name of the pod/service of the kafka broker in the cluster. If you use &lt;a href="https://strimzi.io/"&gt;strimzi&lt;/a&gt;, it would look something like &lt;code&gt;&amp;lt;cluster-name&amp;gt;-kafka-bootstrap:9092&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now, you just need to port-forward the &lt;code&gt;kui&lt;/code&gt; port to access it from your local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on another terminal !&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; myproject port-forward kui 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have kafka-ui available on &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt;. Once you finished, close the terminal running &lt;code&gt;kubectl run&lt;/code&gt; and the &lt;code&gt;kui&lt;/code&gt; pod will be deleted.&lt;/p&gt;

&lt;p&gt;The same logic applies to other tools: run the docker image in the cluster, then port-forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;kubectl run&lt;/code&gt; is one heck of a magic tool to add to your debugging toolbox. As the kafka example shows, it may also be a way to avoid crowding your kubernetes cluster with management tools that are only used once in a while. Don't maintain, just run when needed !&lt;/p&gt;




&lt;p&gt;Written with ❤ by &lt;a href="https://derlin.ch"&gt;derlin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>productivity</category>
    </item>
    <item>
      <title>helmfile: difference between sync and apply (helm 3)</title>
      <dc:creator>Lucy Linder</dc:creator>
      <pubDate>Thu, 26 May 2022 08:57:43 +0000</pubDate>
      <link>https://community.ops.io/derlin/helmfile-difference-between-sync-and-apply-helm-3-2op1</link>
      <guid>https://community.ops.io/derlin/helmfile-difference-between-sync-and-apply-helm-3-2op1</guid>
      <description>&lt;p&gt;&lt;a href="https://helm.sh/"&gt;Helm&lt;/a&gt; and &lt;a href="https://github.com/roboll/helmfile"&gt;helmfile&lt;/a&gt; are great tools to automate kubernetes deployments. However, they have some subtleties that are sometimes hard to understand and may lead to catastrophic problems. One of them is the difference between &lt;code&gt;helmfile sync&lt;/code&gt; and &lt;code&gt;helmfile apply&lt;/code&gt;, a question raised many times, for example in &lt;a href="https://stackoverflow.com/questions/59703760/helmfile-sync-vs-helmfile-apply"&gt;StackOverflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;helmfile -h&lt;/code&gt;, the explanation of those two commands is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;sync&lt;/code&gt; → sync all resources from state file (repos, releases and chart deps)&lt;br&gt;
&lt;code&gt;apply&lt;/code&gt; → apply all resources from state file only when there are changes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But what does it &lt;em&gt;mean&lt;/em&gt; exactly ? What are the differences and pitfalls ? Let's dive in together, starting at the basics of Helm 3 up to helmfile.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: if you are familiar with how Helm 3 upgrades work, you can skip directly to the last section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helm states
&lt;/h2&gt;

&lt;p&gt;The first thing to understand is how Helm stores &lt;em&gt;states&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Helm generates the Kubernetes manifests to apply to a Kubernetes cluster by "compiling" a Chart's templates against some values, that can come from the chart's &lt;code&gt;values.yaml&lt;/code&gt; or the values override (defined in helmfile, passed using the &lt;code&gt;--set&lt;/code&gt; option of the cli, etc.). All those information together (chart, values, options) are what we will call a &lt;em&gt;state&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Whenever you install a release, Helm stores this state in a secret called (the &lt;code&gt;v1&lt;/code&gt; suffix being the &lt;em&gt;revision&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;helm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;RELEASE_NAME&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This secret is simply a compressed, base64-encoded JSON stored in a single key - &lt;code&gt;release&lt;/code&gt; -, which contains &lt;em&gt;everything&lt;/em&gt; needed to reconstruct exactly the helm chart, and to re-apply it with &lt;em&gt;exactly&lt;/em&gt; the same values to reconstruct the release.&lt;/p&gt;

&lt;p&gt;It can be inspected using the following command (see &lt;a href="https://gist.github.com/DzeryCZ/c4adf39d4a1a99ae6e594a183628eaee"&gt;this gist&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get secret sh.helm.release.v1.&amp;lt;RELEASE_NAME&amp;gt;.v&amp;lt;REV&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.release}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;
  Content of the helm-release secret
  &lt;br&gt;
If you decode this secret, you'll see a JSON that contains:

&lt;ul&gt;
&lt;li&gt;name, namespace, and version of the release&lt;/li&gt;
&lt;li&gt;list of all chart files (name + base64 content of all files, excluding &lt;code&gt;templates/*&lt;/code&gt;, &lt;code&gt;Chart.yaml&lt;/code&gt; and &lt;code&gt;values.yaml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;metadata (content of &lt;code&gt;Chart.yaml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;values (content of &lt;code&gt;values.yaml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;templates files (name + base64 content)&lt;/li&gt;
&lt;li&gt;config (value overrides via cmd or helmfile)&lt;/li&gt;
&lt;li&gt;values schema (content of &lt;code&gt;values.schema.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;hooks&lt;/li&gt;
&lt;li&gt;info → current state of the release in Kubernetes (e.g. "&lt;em&gt;install complete&lt;/em&gt;"), dates of first/last deployment, etc.&lt;/li&gt;
&lt;li&gt;actual Kubernetes manifest (output of all rendered templates, this time in plain text)
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;When you upgrade a release, the new state is stored in a new secret, with the version incremented to the new revision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;helm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;RELEASE_NAME&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;v&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;REVISION&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How helm 3 upgrade/rollback works
&lt;/h2&gt;

&lt;p&gt;Now, let's understand how Helm decides what to do during an upgrade.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;em&gt;Every time you run &lt;code&gt;helm upgrade&lt;/code&gt; or &lt;code&gt;helm rollback&lt;/code&gt;, a new revision (and secret) is always created, whether or not there are changes&lt;/em&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Helm 2 and two-way merge
&lt;/h4&gt;

&lt;p&gt;Back in &lt;strong&gt;Helm 2&lt;/strong&gt;, the upgrade process was plain and simple: helm reconstructed the &lt;em&gt;old state&lt;/em&gt; by decoding the helm-release secret of the current revision, and compared it with the &lt;em&gt;desired state&lt;/em&gt; to create the different patches to apply. This is known as &lt;strong&gt;two-way merge&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;old state → desired state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;desired state&lt;/em&gt; can be reconstructed from a helm-release secret (rollback), or from a new version of the chart + values (upgrade).&lt;/p&gt;

&lt;p&gt;The important point is that &lt;strong&gt;Helm 2 didn't take the &lt;em&gt;live state&lt;/em&gt; into account&lt;/strong&gt;, that is, what is effectively present in the cluster. In other words, if you modified anything manually (add a value to a ConfigMap, or a sidecar container in a deployment), this change was not seen at all by Helm, and could either be left as-is, disappear, or be overwritten depending on Helm old/desired states.&lt;/p&gt;

&lt;h4&gt;
  
  
  Helm 3 and three-way strategic merge
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Helm 3&lt;/strong&gt; introduced a brand new way of computing patches required for upgrades and rollbacks, known as &lt;strong&gt;three-way strategic merge&lt;/strong&gt;.&lt;br&gt;
The article &lt;a href="https://blog.plasticscm.com/2016/02/three-way-merging-look-under-hood.html"&gt;Three-way merging: A look under the hood&lt;/a&gt;, gives a good explanation of what three-way merging means (heavily used in git), while Helm doc's section &lt;a href="https://helm.sh/docs/faq/changes_since_helm2/#improved-upgrade-strategy-3-way-strategic-merge-patches"&gt;Improved Upgrade Strategy: 3-way Strategic Merge Patches&lt;/a&gt; focuses more on what it means in Helm.&lt;/p&gt;

&lt;p&gt;But simply put, &lt;strong&gt;Helm 3 now takes the &lt;em&gt;live state&lt;/em&gt; into account&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(old state → desired state) → (live state → desired state)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules are (fields = key+value):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;+&lt;/code&gt; (add) → new fields in the &lt;em&gt;desired state&lt;/em&gt; not present in the &lt;em&gt;old state&lt;/em&gt; are added (overwriting any &lt;em&gt;live state&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;⌫&lt;/code&gt; (remove) → fields existing in the &lt;em&gt;old state&lt;/em&gt; that are not present in the &lt;em&gt;desired state&lt;/em&gt; are removed (even if their value changed in the &lt;em&gt;live state&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;±&lt;/code&gt; (overwrite) → fields in the &lt;em&gt;live state&lt;/em&gt; that are also present in the &lt;em&gt;desired state&lt;/em&gt; but have a different value are updated (whatever the &lt;em&gt;old state&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;∅&lt;/code&gt; (ignore) → the rest is left unchanged (e.g. new fields in the &lt;em&gt;live state&lt;/em&gt; stay)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Moreover, the patches do &lt;em&gt;merge&lt;/em&gt; operations, meaning maps are deep-merged (vs completely replaced). This is a huge improvement from Helm 2. Among others, it means that in Helm 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is possible to add a sidecar container to a deployment manually, or a new data entry in a ConfigMap. If they are not managed by Helm (no fields in the generated manifests about it), they will stay unchanged after upgrade/rollback;&lt;/li&gt;
&lt;li&gt;if you modify fields managed by Helm manually, doing a rollback will effectively reset the fields to the Helm values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  Simple example
  &lt;br&gt;
Let's say you install a deployment with Helm with the following labels (manifest stripped for readability):&lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;label-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;install&lt;/span&gt;
        &lt;span class="na"&gt;label-2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;install&lt;/span&gt;
        &lt;span class="na"&gt;label-3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;install&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, you change the labels manually to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;label-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
  &lt;span class="na"&gt;label-2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
  &lt;span class="na"&gt;label-3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manual&lt;/span&gt;
  &lt;span class="na"&gt;new-one&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;added&lt;/span&gt;   &lt;span class="c1"&gt;# also add one&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally do a Helm upgrade, with the new values being:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;label-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;install&lt;/span&gt;  &lt;span class="c1"&gt;# no change&lt;/span&gt;
  &lt;span class="na"&gt;label-2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upgrade&lt;/span&gt;  &lt;span class="c1"&gt;# change&lt;/span&gt;
                    &lt;span class="c1"&gt;# delete&lt;/span&gt;
  &lt;span class="na"&gt;label-4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upgrade&lt;/span&gt;  &lt;span class="c1"&gt;# add&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;label-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;install&lt;/span&gt;  &lt;span class="c1"&gt;# overwritten&lt;/span&gt;
  &lt;span class="na"&gt;label-2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upgrade&lt;/span&gt;  &lt;span class="c1"&gt;# overwritten&lt;/span&gt;
                    &lt;span class="c1"&gt;# deleted&lt;/span&gt;
  &lt;span class="na"&gt;label-4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upgrade&lt;/span&gt;  &lt;span class="c1"&gt;# added&lt;/span&gt;
  &lt;span class="na"&gt;new-one&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;added&lt;/span&gt;    &lt;span class="c1"&gt;# ignored/kept&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Helmfile: sync vs apply
&lt;/h2&gt;

&lt;p&gt;Now that we understand how helm upgrades work, let's dive into helmfile sync vs apply.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;helmfile sync&lt;/code&gt; command will &lt;strong&gt;run &lt;code&gt;helm upgrade&lt;/code&gt; on all releases&lt;/strong&gt;. This means all releases will have their revision incremented by one. However, as Helm does three-way strategic merges, if there is no change between the live and desired state, no patch will actually be applied: there is just a new helm-release secret created.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;helmfile apply&lt;/code&gt; command will &lt;strong&gt;run &lt;code&gt;helm upgrade&lt;/code&gt; only when there are changes&lt;/strong&gt;.&lt;br&gt;
To detect changes, helmfile uses the &lt;a href="https://github.com/databus23/helm-diff"&gt;&lt;strong&gt;helm-diff&lt;/strong&gt;&lt;/a&gt; plugin. For a long time, helm-diff only computed the difference between the &lt;em&gt;old&lt;/em&gt; vs &lt;em&gt;desired state&lt;/em&gt;; it &lt;strong&gt;didn't look at the &lt;em&gt;live state&lt;/em&gt;&lt;/strong&gt; (similar to Helm 2). If something changed outside of Helm, helm-diff would return &lt;em&gt;"no change"&lt;/em&gt;, and the release won't be upgraded.&lt;/p&gt;

&lt;p&gt;helm-diff &lt;strong&gt;added support for three-way merge diffs on &lt;a href="https://github.com/databus23/helm-diff/tree/v3.3.0"&gt;v3.3.0&lt;/a&gt; (January 10, 2022)&lt;/strong&gt;. As the helm-diff process inherits environment from the helmfile process, it is now possible to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# use three-way merge strategy for diffing&lt;/span&gt;
&lt;span class="nv"&gt;HELM_DIFF_THREE_WAY_MERGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;helmfile apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, &lt;code&gt;helmfile apply&lt;/code&gt; can now detect and change manual changes as well.&lt;/p&gt;

&lt;p&gt;In other words, &lt;code&gt;apply&lt;/code&gt; has the advantage of not creating useless new revisions, but doesn't guarantee the coherency of the &lt;em&gt;live state&lt;/em&gt;, as manual changes may go undetected unless the helm-diff plugin is properly configured. &lt;code&gt;sync&lt;/code&gt; is exactly the opposite: it always creates new revisions for &lt;em&gt;all&lt;/em&gt; releases, but will detect and undo any manual change that happened on Helm-managed fields.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sync&lt;/code&gt; or &lt;code&gt;apply&lt;/code&gt; is thus down to a trade-off, which is alleviated if you decide to never change anything manually (or always use three-way merge diffs). If you stick with this best practice (or always export the &lt;code&gt;HELM_DIFF_THREE_WAY_MERGE&lt;/code&gt; environment variable), &lt;code&gt;apply&lt;/code&gt; is always the way to go.&lt;/p&gt;




&lt;p&gt;Written with ❤ by &lt;a href="https://github.com/derlin"&gt;derlin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>helm</category>
      <category>helmfile</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
