<?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>Forem: Brian Michalski</title>
    <description>The latest articles on Forem by Brian Michalski (@bamnet).</description>
    <link>https://forem.com/bamnet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F284976%2F17f56e0f-9ba1-479f-80fe-8c66c97441e3.jpg</url>
      <title>Forem: Brian Michalski</title>
      <link>https://forem.com/bamnet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bamnet"/>
    <language>en</language>
    <item>
      <title>Stop Hardcoding Google Maps API Keys!</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Wed, 12 Jun 2024 04:15:41 +0000</pubDate>
      <link>https://forem.com/bamnet/stop-hardcoding-maps-platform-api-keys-5c2n</link>
      <guid>https://forem.com/bamnet/stop-hardcoding-maps-platform-api-keys-5c2n</guid>
      <description>&lt;p&gt;One of the really cool things about the new suite of APIs that Google Maps Platform has been releasing lately (&lt;a href="https://developers.google.com/maps/documentation/routes" rel="noopener noreferrer"&gt;Routes API&lt;/a&gt;, &lt;a href="https://developers.google.com/maps/documentation/places/web-service/op-overview" rel="noopener noreferrer"&gt;Places API&lt;/a&gt;, etc) is that they look and feel a lot like other Google Cloud Platform APIs. They're exposed via gRPC, support field masks, and let developers authenticate via OAuth. A really helpful side effects of OAuth is support for JSON Web Tokens (JWT) credentials which allow apps to do a much better job securing client-side applications.&lt;/p&gt;

&lt;p&gt;Classic Google Maps APIs rely on a a hardcoded API key which is not great. Hardcoding passwords was cool in 2005 (maybe?) but it's 2024, we can do better.&lt;/p&gt;

&lt;p&gt;Using a small snippet of code, our app can generate a unique auth token for each website visitor that will expire after a defined period. Even if the user maliciously "borrowed" that token, it would only be valid until the expiration period you specified.&lt;/p&gt;

&lt;h2&gt;
  
  
  JWT Intro
&lt;/h2&gt;

&lt;p&gt;If you're new to JWTs like I am, they are base64 encoded JSON blobs that get signed using a private key. APIs can read contents of that JSON object and verify the signature to authenticate them. Here's what one looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJSUzI1NiIsImtpZCI6ImVlODk1OWMzYzFhMDdlMTBlZGJjMDE3NWI2ZmZmN2I1ZGYyOTBiZTIiLCJ0eXAiOiJKV1QifQ.eyJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZ2VvLXBsYXRmb3JtLnJvdXRlcyIsImlzcyI6InVidW50dS12bUBob2xpZGF5cy0xMTcwLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic3ViIjoidWJ1bnR1LXZtQGhvbGlkYXlzLTExNzAuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwczovL3JvdXRlcy5nb29nbGVhcGlzLmNvbS8iLCJleHAiOjE2Njc4ODQ0NTIsImlhdCI6MTY2Nzg4NDMzMn0.Zey0GtvSH78_xfBTNL-Ij0qm1dK9wqDc5nllYLPZyWNp_V5sYVKaPpWSjJ2IRVHBdhKBLYgXVKLty7Dlo0BMW9SJ4eexIxmdM8IR3CeH5SmYLl4pQxV3S8eO_5T41B6LCD49gKTtlXIWvtCoGitWDSYiFCZauf2zoIEa5XZ_TkazMr1DGYbc9w8UvtXVARAby2WRbSiHyqkjSsAU5HoKClKhaw7NaP1vNJ-7IlpTz9t-sTSZwl-6wur65gI_FtAGiohWPUILRY-YKMhb_wXQ5AtlDUmvGKdqNzuXBMmk8-iiQTwmYPuWQBNt0MtK7hfghWyWubUjBfT0t4yiGSrHmA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you &lt;a href="https://jwt.io/#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImVlODk1OWMzYzFhMDdlMTBlZGJjMDE3NWI2ZmZmN2I1ZGYyOTBiZTIiLCJ0eXAiOiJKV1QifQ.eyJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZ2VvLXBsYXRmb3JtLnJvdXRlcyIsImlzcyI6InVidW50dS12bUBob2xpZGF5cy0xMTcwLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic3ViIjoidWJ1bnR1LXZtQGhvbGlkYXlzLTExNzAuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwczovL3JvdXRlcy5nb29nbGVhcGlzLmNvbS8iLCJleHAiOjE2Njc4ODQ0NTIsImlhdCI6MTY2Nzg4NDMzMn0.Zey0GtvSH78_xfBTNL-Ij0qm1dK9wqDc5nllYLPZyWNp_V5sYVKaPpWSjJ2IRVHBdhKBLYgXVKLty7Dlo0BMW9SJ4eexIxmdM8IR3CeH5SmYLl4pQxV3S8eO_5T41B6LCD49gKTtlXIWvtCoGitWDSYiFCZauf2zoIEa5XZ_TkazMr1DGYbc9w8UvtXVARAby2WRbSiHyqkjSsAU5HoKClKhaw7NaP1vNJ-7IlpTz9t-sTSZwl-6wur65gI_FtAGiohWPUILRY-YKMhb_wXQ5AtlDUmvGKdqNzuXBMmk8-iiQTwmYPuWQBNt0MtK7hfghWyWubUjBfT0t4yiGSrHmA" rel="noopener noreferrer"&gt;decode&lt;/a&gt; that token, you can see it contains information about the key it was signed with:&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;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ee8959c3c1a07e10edbc0175b6fff7b5df290be2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JWT"&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;and, more importantly, payload with information about who issued the token, what it can be used for, and when it will expire:&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;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/auth/geo-platform.routes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://routes.googleapis.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1667880959&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1667880839&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ubuntu-vm@holidays-1170.iam.gserviceaccount.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ubuntu-vm@holidays-1170.iam.gserviceaccount.com"&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;In my experience, most Google Maps Platform APIs expects a token to contain a payload with the following fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;field&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expiration time for the token.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issued time for the token (aka now).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aud&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API endpoint the token is intended for, like &lt;code&gt;https://routes.googleapis.com/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scopes, separated by spaces, this token can be used for, like &lt;code&gt;https://www.googleapis.com/auth/geo-platform.routes&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;From what I can tell, either a &lt;code&gt;scope&lt;/code&gt; or &lt;code&gt;aud&lt;/code&gt;ience field needs to be set. I don't know what's the "right" way.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've collected a bunch of options for Scope and Audience &lt;a href="https://github.com/bamnet/gmp-jwt/blob/main/apis/apis.go" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating a JWT
&lt;/h2&gt;

&lt;p&gt;The biggest downside to JWTs is that you have to generate them on the fly - you can't just hardcode them into your app like you could with good old AIza. Generating a JWT involves building an JSON object with the right fields (see above), signing it, and then base64 encoding it to a string. &lt;a href="https://jwt.io/introduction" rel="noopener noreferrer"&gt;https://jwt.io/introduction&lt;/a&gt; walks through this in glorious detail.&lt;/p&gt;

&lt;p&gt;To make that step easier, I created a little Go backend which will generate tokens and sign them using a GCP Service Account: &lt;a href="https://github.com/bamnet/gmp-jwt" rel="noopener noreferrer"&gt;https://github.com/bamnet/gmp-jwt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Running this backend somewhere like Cloud Run makes it easy to start generating tokens since you get access to a Default Service Account but you can run this anywhere and set &lt;a href="https://cloud.google.com/docs/authentication/application-default-credentials" rel="noopener noreferrer"&gt;Application Default Credentials&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  But what protects the JWT generator?
&lt;/h3&gt;

&lt;p&gt;Great question. Even though our JWT tokens are tightly scoped and moderately TTLed, an attacker could just scrape them from our backend that generates them. That would be no fun.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://firebase.google.com/docs/app-check" rel="noopener noreferrer"&gt;Firebase AppCheck&lt;/a&gt; we can verify that the environment requesting a token looks legit before giving them a JWT.&lt;/p&gt;

&lt;p&gt;The frontend just needs to include recaptcha v3 (which is totally silent now - no more traffic lights, cross walks, or bicycles) and a bit of Firebase code to wire up the token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending a JWT to Google Maps Platform
&lt;/h2&gt;

&lt;p&gt;To authenticate using a JWT to a modern Google Maps Platform API, we use Bearer Authentication. Set the &lt;code&gt;Authorization&lt;/code&gt; header to &lt;code&gt;Bearer ${token}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In JavaScript, that snippet might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://routes.googleapis.com/directions/v2:computeRoutes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImVlODk1OWMzYzFhMDdlMTBlZGJjMDE3NWI2ZmZmN2I1ZGYyOTBiZTIiLCJ0eXAiOiJKV1QifQ.eyJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZ2VvLXBsYXRmb3JtLnJvdXRlcyIsImlzcyI6InVidW50dS12bUBob2xpZGF5cy0xMTcwLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic3ViIjoidWJ1bnR1LXZtQGhvbGlkYXlzLTExNzAuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwczovL3JvdXRlcy5nb29nbGVhcGlzLmNvbS8iLCJleHAiOjE2Njc4ODQ0NTIsImlhdCI6MTY2Nzg4NDMzMn0.Zey0GtvSH78_xfBTNL-Ij0qm1dK9wqDc5nllYLPZyWNp_V5sYVKaPpWSjJ2IRVHBdhKBLYgXVKLty7Dlo0BMW9SJ4eexIxmdM8IR3CeH5SmYLl4pQxV3S8eO_5T41B6LCD49gKTtlXIWvtCoGitWDSYiFCZauf2zoIEa5XZ_TkazMr1DGYbc9w8UvtXVARAby2WRbSiHyqkjSsAU5HoKClKhaw7NaP1vNJ-7IlpTz9t-sTSZwl-6wur65gI_FtAGiohWPUILRY-YKMhb_wXQ5AtlDUmvGKdqNzuXBMmk8-iiQTwmYPuWQBNt0MtK7hfghWyWubUjBfT0t4yiGSrHmA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Goog-FieldMask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;routes.duration,routes.distanceMeters&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Routes API request body.&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;I wouldn't dream of sharing an API Key in a blogpost but an expired JWT, no problem!&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server-Side
&lt;/h3&gt;

&lt;p&gt;You need to deploy a server endpoint somewhere which will mint JWTs, optionally after checking Firebase App Check. A sample Cloud Run function I use is @ &lt;a href="https://github.com/bamnet/gmp-jwt" rel="noopener noreferrer"&gt;https://github.com/bamnet/gmp-jwt&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-Side
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;(optional) Get an app check token from Firebase.&lt;/li&gt;
&lt;li&gt;Requests a JWT from the server endpoint you deployed above, optionally passing that app check token from Step 1.&lt;/li&gt;
&lt;li&gt;Call the desired Google Maps Platform API passing &lt;code&gt;Authorization: Bearer ${token}&lt;/code&gt;, passing the token from Step 2.&lt;/li&gt;
&lt;li&gt;???&lt;/li&gt;
&lt;li&gt;Profit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's an example in JS, but you can do the same thing in Android &amp;amp; iOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Initialize Firebase.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firebaseConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize AppCheck.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeAppCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReCaptchaV3Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/** reCAPTCHA Key */&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;isTokenAutoRefreshEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Grab an AppCheck token.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appCheckToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appCheck&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Call our backend to convert the AppCheck token into a JWT.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/** JWT Minting Backend */&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Firebase-AppCheck&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appCheckToken&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Call the Routes API.&lt;/span&gt;
&lt;span class="c1"&gt;// Look ma, no hardcoded API key!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://routes.googleapis.com/directions/v2:computeRoutes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Pass our JWT!&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Goog-FieldMask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;routes.duration,routes.distanceMeters&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Routes API request body.&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Ta-Da, no more hardcoded API key!&lt;/p&gt;

&lt;h3&gt;
  
  
  Not-So-Frequently Asked Questions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Can a single JWT be used calling multiple APIs?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. In my tests, a token can have multiple scopes, but only 1 audience. Scopes are separated with spaces.  To generate a token for both Places and Routes, you'd set:&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;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/auth/geo-platform.routes https://www.googleapis.com/auth/maps-platform.places"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&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;&lt;strong&gt;Couldn't you proxy all requests through a trusted server which added &amp;amp; removed an API key?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, but then you have a dependency in the serving path for ~every request to an API. That adds some marginal latency and could have scaling challenges for high-QPS APIs like Map Tiles or Places Autocomplete.  That also means the proxy servers will see all requests which might mean more privacy / compliance work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work for the Maps JavaScript API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No, but that sounds like a good &lt;a href="https://issuetracker.google.com/savedsearches/558438" rel="noopener noreferrer"&gt;feature request&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>googlemaps</category>
      <category>jwt</category>
      <category>firebase</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>GRPC Performance in Flutter</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Mon, 11 Apr 2022 03:49:55 +0000</pubDate>
      <link>https://forem.com/bamnet/grpc-performance-in-flutter-2fbk</link>
      <guid>https://forem.com/bamnet/grpc-performance-in-flutter-2fbk</guid>
      <description>&lt;p&gt;&lt;a href="https://firebase.google.com/docs/perf-mon" rel="noopener noreferrer"&gt;Firebase Performance Monitoring&lt;/a&gt; is a quick and easy way to get started monitoring the performance of your apps. It automatically monitors a variety of metrics, including HTTP(S) requests which is a great way to compliment your server-side metrics and understand how your app's users are actually feeling.&lt;/p&gt;

&lt;p&gt;That magic doesn't kick in automatically if you're using GRPC to make requests in your Flutter app.  Instead, you can drop in a quick interceptor to measure the performance of GRPC requests and report them to Firebase:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lib/performance_interceptor.dart&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:firebase_performance/firebase_performance.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:grpc/grpc.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PerformanceInterceptor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="n"&gt;ClientInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;FirebasePerformance&lt;/span&gt; &lt;span class="n"&gt;_performance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FirebasePerformance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;PerformanceInterceptor&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{}]);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;ResponseFuture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;interceptUnary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;ClientMethod&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;CallOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClientUnaryInvoker&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;invoker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Trace&lt;/span&gt; &lt;span class="n"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_performance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&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="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putAttribute&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="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;ResponseFuture&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invoker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&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;metric&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&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;resp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;ResponseStream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;interceptStreaming&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
      &lt;span class="n"&gt;ClientMethod&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;&amp;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;CallOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;ClientStreamingInvoker&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;invoker&lt;/span&gt;&lt;span class="p"&gt;)&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;invoker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&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;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This class is super simple to use, just pass it alongside the options and channel when constructing your GRPC client:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'performance_interceptor.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GreeterClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;interceptors:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;PerformanceInterceptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;You can also pass attributes (dimensions) that should be passed with each request.  I like to pass the server I'm connecting to:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GreeterClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;interceptors:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;PerformanceInterceptor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'host'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Presto!  You've now got performance metric for all your simple GRPC requests!&lt;/p&gt;

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

</description>
      <category>flutter</category>
      <category>firebase</category>
      <category>grpc</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Static Maps At Night</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Fri, 16 Apr 2021 02:22:12 +0000</pubDate>
      <link>https://forem.com/bamnet/static-maps-at-night-2ebd</link>
      <guid>https://forem.com/bamnet/static-maps-at-night-2ebd</guid>
      <description>&lt;p&gt;Google's &lt;a href="https://developers.google.com/maps/documentation/maps-static/overview"&gt;Static Maps API&lt;/a&gt; renders a map that looks great against a white background and other light websites but stands out like a bright TV in a dark room when your users switch over to dark / night mode - ouch!&lt;/p&gt;

&lt;p&gt;Traditional techniques for making images dark mode friendly involve using &lt;a href="https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web/#dark-mode-images"&gt;CSS filters&lt;/a&gt; to reduce the brightness and up the contrast which dulls the color a bunch but doesn't give us a completely dark map like you may see on your phone:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei3y5phy5q5r0m9zlx4r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei3y5phy5q5r0m9zlx4r.jpg" alt="Static Map w/ CSS filters" width="595" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can do something more clever using some basic HTML tags and the Static Map API's styling features.&lt;/p&gt;

&lt;p&gt;Given an image tag like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;img src="https://maps.googleapis.com/maps/api/staticmap?..."&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can wrap it in a &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element that is smart enough to swap to a styled static map when the browser is requesting dark mode. To get that dark mode styling, we can add &lt;code&gt;style&lt;/code&gt; tags to change all the light map features to night-friendly colors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(prefers-color-scheme: dark)"&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"https://maps.googleapis.com/maps/api/staticmap?&amp;lt;YOUR_MAP_PARAMS&amp;gt;&amp;amp;style=element%3Ageometry%7Ccolor%3A0x242f3e&amp;amp;style=element%3Alabels.text.stroke%7Ccolor%3A0x242f3e&amp;amp;style=element%3Alabels.text.fill%7Ccolor%3A0x746855&amp;amp;style=feature%3Aadministrative.locality%7Celement%3Alabels.text.fill%7Ccolor%3A0xd59563&amp;amp;style=feature%3Apoi%7Celement%3Alabels.text.fill%7Ccolor%3A0xd59563&amp;amp;style=feature%3Apoi.park%7Celement%3Ageometry%7Ccolor%3A0x263c3f&amp;amp;style=feature%3Apoi.park%7Celement%3Alabels.text.fill%7Ccolor%3A0x6b9a76&amp;amp;style=feature%3Aroad%7Celement%3Ageometry%7Ccolor%3A0x38414e&amp;amp;style=feature%3Aroad%7Celement%3Ageometry.stroke%7Ccolor%3A0x212a37&amp;amp;style=feature%3Aroad%7Celement%3Alabels.text.fill%7Ccolor%3A0x9ca5b3&amp;amp;style=feature%3Aroad.highway%7Celement%3Ageometry%7Ccolor%3A0x746855&amp;amp;style=feature%3Aroad.highway%7Celement%3Ageometry.stroke%7Ccolor%3A0x1f2835&amp;amp;style=feature%3Aroad.highway%7Celement%3Alabels.text.fill%7Ccolor%3A0xf3d19c&amp;amp;style=feature%3Atransit%7Celement%3Ageometry%7Ccolor%3A0x2f3948&amp;amp;style=feature%3Atransit.station%7Celement%3Alabels.text.fill%7Ccolor%3A0xd59563&amp;amp;style=feature%3Awater%7Celement%3Ageometry%7Ccolor%3A0x17263c&amp;amp;style=feature%3Awater%7Celement%3Alabels.text.fill%7Ccolor%3A0x515c6d&amp;amp;style=feature%3Awater%7Celement%3Alabels.text.stroke%7Ccolor%3A0x17263c"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://maps.googleapis.com/maps/api/staticmap?&amp;lt;YOUR_MAP_PARAMS&amp;gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part of that &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; tag is the &lt;strong&gt;&lt;code&gt;media="(prefers-color-scheme: dark)"&lt;/code&gt;&lt;/strong&gt; to indicate it should be shown during dark times and appending all the &lt;strong&gt;style&lt;/strong&gt; tags (below) which encode dark-friendly map styles.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;style=element%3Ageometry%7Ccolor%3A0x242f3e&amp;amp;style=element%3Alabels.text.stroke%7Ccolor%3A0x242f3e&amp;amp;style=element%3Alabels.text.fill%7Ccolor%3A0x746855&amp;amp;style=feature%3Aadministrative.locality%7Celement%3Alabels.text.fill%7Ccolor%3A0xd59563&amp;amp;style=feature%3Apoi%7Celement%3Alabels.text.fill%7Ccolor%3A0xd59563&amp;amp;style=feature%3Apoi.park%7Celement%3Ageometry%7Ccolor%3A0x263c3f&amp;amp;style=feature%3Apoi.park%7Celement%3Alabels.text.fill%7Ccolor%3A0x6b9a76&amp;amp;style=feature%3Aroad%7Celement%3Ageometry%7Ccolor%3A0x38414e&amp;amp;style=feature%3Aroad%7Celement%3Ageometry.stroke%7Ccolor%3A0x212a37&amp;amp;style=feature%3Aroad%7Celement%3Alabels.text.fill%7Ccolor%3A0x9ca5b3&amp;amp;style=feature%3Aroad.highway%7Celement%3Ageometry%7Ccolor%3A0x746855&amp;amp;style=feature%3Aroad.highway%7Celement%3Ageometry.stroke%7Ccolor%3A0x1f2835&amp;amp;style=feature%3Aroad.highway%7Celement%3Alabels.text.fill%7Ccolor%3A0xf3d19c&amp;amp;style=feature%3Atransit%7Celement%3Ageometry%7Ccolor%3A0x2f3948&amp;amp;style=feature%3Atransit.station%7Celement%3Alabels.text.fill%7Ccolor%3A0xd59563&amp;amp;style=feature%3Awater%7Celement%3Ageometry%7Ccolor%3A0x17263c&amp;amp;style=feature%3Awater%7Celement%3Alabels.text.fill%7Ccolor%3A0x515c6d&amp;amp;style=feature%3Awater%7Celement%3Alabels.text.stroke%7Ccolor%3A0x17263&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp1r1kcmh45hdc7cnppl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp1r1kcmh45hdc7cnppl.png" alt="Map showing dark mode style" width="600" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Presto! When users visit their site in a browser supporting night mode, the media query will kick in and render the styled map. Users browsing in light mode (or without support for night mode) will see the regular style. The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element is smart enough to only load 1 image, so you shouldn't be charged twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; If you're using URL Signing to better secure your Static Maps requests, you will need to generate a new signature for the styled map!&lt;/p&gt;

&lt;p&gt;Here's a JSFiddle that shows it all in action, you'll see a light or dark map depending on what your browser is currently requesting.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://jsfiddle.net/bamnet/4hfw35v2//embedded/html,result//dark" width="100%" height="600"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>googlemaps</category>
      <category>googlecloud</category>
      <category>maps</category>
      <category>darkmode</category>
    </item>
    <item>
      <title>Practical Differential Privacy w/ Apache Beam</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Wed, 02 Dec 2020 03:59:29 +0000</pubDate>
      <link>https://forem.com/bamnet/practical-differential-privacy-w-apache-beam-4bki</link>
      <guid>https://forem.com/bamnet/practical-differential-privacy-w-apache-beam-4bki</guid>
      <description>&lt;p&gt;One of the most durable techniques to protect user privacy is through differential privacy. In a &lt;a href="https://dev.to/bamnet/go-bigquery-beam-for-beginners-1p5i"&gt;previous post&lt;/a&gt;, we explored how to build an Apache Beam pipeline that extracted and counted ngrams from HackerNews comments. Today, we'll take the same pipeline and upgrade it with some differential privacy goodness using &lt;a href="https://github.com/google/differential-privacy/tree/main/privacy-on-beam"&gt;Privacy-on-Beam&lt;/a&gt; from Google's &lt;a href="https://github.com/google/differential-privacy"&gt;differential privacy library&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Identifying the User
&lt;/h2&gt;

&lt;p&gt;Counterintuitively, adding differential privacy to a Beam pipeline requires you to have an ID for each user. You don't need to know &lt;em&gt;who&lt;/em&gt; the user is, you just need access to a stable identifier for them in your dataset. Good starting choices include that autoincrementing user_id field or a username/email address. The ID you pick should map as closely as possible to the entity whose privacy you are trying to protect.&lt;/p&gt;

&lt;p&gt;💁&lt;em&gt;Tip: To err on the safe side, consider hashing or encrypting this ID to prevent yourself from accidentally logging it or debugging with it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since we're using HackerNews comments, the &lt;code&gt;author&lt;/code&gt; field is a pretty good choice. We'll start with some changes to grab the author for each comment and propagate that user along through the ngram extraction.&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="c"&gt;// CommentRow models 1 row of HackerNews comments.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CommentRow&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`bigquery:"author"`&lt;/span&gt;
    &lt;span class="n"&gt;Text&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`bigquery:"text"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// AuthorNgram represents an ngram and it's author.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AuthorNgram&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Author&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Ngram&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;`SELECT author, text
FROM `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"`bigquery-public-data.hacker_news.comments`"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;`
WHERE time_ts BETWEEN '2013-01-01' AND '2014-01-01'
AND author IS NOT NULL AND text IS NOT NULL
LIMIT 1000
`&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// ...&lt;/span&gt;

    &lt;span class="n"&gt;authorNgrams&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParDo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;CommentRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorNgram&lt;/span&gt;&lt;span class="p"&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gram&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ngram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthorNgram&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ngram&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gram&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;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Setup Privacy Budget
&lt;/h2&gt;

&lt;p&gt;In differential privacy-land, epsilon and delta are the main ways of controlling how much can be learned about any specific user. &lt;strong&gt;Bigger numbers = less privacy&lt;/strong&gt;. For our pipeline, we'll pick sample values of epsilon = 4 and delta = 0.0001.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why 4 and 10&lt;sup&gt;-4&lt;/sup&gt;? I don't know. Apple uses an ε=4 according to it's &lt;a href="https://www.apple.com/privacy/docs/Differential_Privacy_Overview.pdf"&gt;Differential Privacy Overview&lt;/a&gt;. I'd like to write a post on how to pick these numbers once I learn more.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Configure differential privacy parameters.&lt;/span&gt;
&lt;span class="n"&gt;epsilon&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;// ε = 4&lt;/span&gt;
&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pow10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Δ = 1e-4.&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pbeam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPrivacySpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Make Private Data
&lt;/h2&gt;

&lt;p&gt;Apache Beam pipelines use a &lt;code&gt;PCollection&lt;/code&gt; as the primary container for data. Privacy-on-Beam introduces a new container, the &lt;code&gt;PrivatePCollection&lt;/code&gt;, which acts like a &lt;code&gt;PCollection&lt;/code&gt; but knows how to preserve privacy along the way.&lt;/p&gt;

&lt;p&gt;Using the PrivacySpec from Step 2, and the &lt;code&gt;PCollection&amp;lt;AuthorNgrams&amp;gt;&lt;/code&gt; from Step 1, we can build a &lt;code&gt;PrivatePCollection&lt;/code&gt; by letting the library know which field has our user id, in this case, a reference to the &lt;code&gt;Author&lt;/code&gt; field of the &lt;code&gt;AuthorNgram&lt;/code&gt; struct. Passing the string name of a struct field feels a bit weird, but whatever.&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;pgrams&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pbeam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MakePrivateFromStruct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authorNgrams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Author"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Do Stats
&lt;/h2&gt;

&lt;p&gt;In our previous pipeline, counting the ngrams was as simple as &lt;code&gt;stats.Count(s, ngrams)&lt;/code&gt;. Now that we have a &lt;code&gt;PrivatePCollection&lt;/code&gt; there's a bit more work involved.&lt;/p&gt;

&lt;p&gt;First, we need to simplify the data to just the ngram, converting our &lt;code&gt;PrivatePCollection&amp;lt;AuthorNgram&amp;gt;&lt;/code&gt; to a &lt;code&gt;PrivatePCollection&amp;lt;string&amp;gt;&lt;/code&gt;. Behind the scenes, the PrivatePCollection will keep track of the author. We need to call the &lt;code&gt;ParDo&lt;/code&gt; function from the privacy-on-beam package for this transform, not the usual beam one. It works the same.&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;ngrams&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pbeam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParDo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;AuthorNgram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ngram&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;pgrams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With our private pcollection of ngrams we're now ready to Count them. Privacy-on-beam implements its own stat functions which are where all the real magic happens.&lt;/p&gt;

&lt;p&gt;When calling &lt;code&gt;pbeam.Count&lt;/code&gt; we'll also need to pick two more privacy parameters controlling the count behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many partitions (aka ngrams) a user can contribute.&lt;/li&gt;
&lt;li&gt;How many times a user can contribute to one partition (use the same ngram).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To keep it simple, let's say that a user can contribute up to 700 different ngrams and can contribute 2 times to each ngram. In practice, this means if User A makes 5 comments saying "great idea", only 2 of them will be counted. If User B writes enough comments to contribute 701 unique ngrams, 1 of them will be randomly dropped. These parameters help remove outliers from the data which reduces the amount of noise you see in the output.&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;counts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pbeam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngrams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pbeam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CountParams&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;MaxPartitionsContributed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&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;h2&gt;
  
  
  Fin
&lt;/h2&gt;

&lt;p&gt;With that, your upgrade is complete! The &lt;code&gt;counts&lt;/code&gt; returned can be used very close to the old pipeline, it's a &lt;code&gt;PCollection&amp;lt;string, int64&amp;gt;&lt;/code&gt; that you can write to a text file, upload to BigQuery, further manipulate, etc. Unlike the first pipeline though, the ngrams here are differentially private... we'll never know who wrote the HackerNews comments which contributed to them.&lt;/p&gt;

&lt;p&gt;You can find the &lt;a href="https://github.com/bamnet/beamdemos/blob/main/differential_privacy_beam/main.go"&gt;end-to-end code on Github&lt;/a&gt;, and a &lt;a href="https://github.com/bamnet/beamdemos/commit/521e6c841a70cf514817a3674b687174a8045cac"&gt;diff&lt;/a&gt; showing all the changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyo98h8zqmbpnsfqz8a1g.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyo98h8zqmbpnsfqz8a1g.gif" alt="Man waving fingers like a magician" width="275" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⚠️&lt;em&gt;Warning: In my experience running differentially private pipelines generally takes longer and requires more compute resources. Expect longer runtimes and, if you run this on Dataflow, more instances.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>apachebeam</category>
      <category>privacy</category>
      <category>googlecloud</category>
      <category>go</category>
    </item>
    <item>
      <title>Go + BigQuery : Beam for Beginners</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Mon, 03 Aug 2020 03:44:51 +0000</pubDate>
      <link>https://forem.com/bamnet/go-bigquery-beam-for-beginners-1p5i</link>
      <guid>https://forem.com/bamnet/go-bigquery-beam-for-beginners-1p5i</guid>
      <description>&lt;p&gt;Curious how to get started reading from BigQuery using Apache Beam in Go? In this post, we'll walk through the basics of Apache Beam and write a basic Go pipeline that reads from BigQuery and computes some ngrams.&lt;/p&gt;

&lt;h1&gt;
  
  
  Beam Intro
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://beam.apache.org/" rel="noopener noreferrer"&gt;Apache Beam&lt;/a&gt; is an open-source SDK for writing "big data" processing pipelines complete with Python, Java, and Go implementations. If all goes well, you can write a pipeline using Apache Beam and then run it locally or deploy it on The Cloud using GCP Dataflow, Apache Flink, Spark, etc where it can &lt;em&gt;magically scale&lt;/em&gt; up to handle a large amount of data.&lt;/p&gt;

&lt;p&gt;Since Beam pipelines can &lt;em&gt;magically scale&lt;/em&gt; you have to write your pipeline using Beam's special methods and types. This allows your pipeline to be sharded across dozens/hundreds of workers behind the scenes. For beginners there are two key constructs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PCollection&lt;/strong&gt;: A PCollection is a fancy array. Most steps of your pipeline will take a PCollection as input, run a function on each element, and put that output in another PCollection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ParDo&lt;/strong&gt;: A ParDo is a function that runs on each PCollection element. When it runs, it can append one or more elements to the resulting PCollection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: This is an oversimplified introduction to Apache Beam. Fancier operations like group/combine/join require more functions you can learn about in the &lt;a href="https://godoc.org/github.com/apache/beam/sdks/go/pkg/beam" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: Boring Boilerplate
&lt;/h1&gt;

&lt;p&gt;There is a bunch of boilerplate code that will import beam, construct a new pipeline (&lt;code&gt;beam.NewPipeline&lt;/code&gt;), and execute it (&lt;code&gt;beamx.Run&lt;/code&gt;). In the middle, we've left space to build all the pieces of the pipeline.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

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

    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/log"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/x/beamx"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPipeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Build the pipeline here.&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beamx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exitf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to execute job: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h1&gt;
  
  
  Step 2: Reading from BigQuery
&lt;/h1&gt;

&lt;p&gt;Pipelines written in Go read from BigQuery just like most other Go programs, running a SQL query and decoding the results into &lt;code&gt;structs&lt;/code&gt; that match the returned fields.&lt;/p&gt;

&lt;p&gt;For our example, we're going to be reading HackerNews comments from the BigQuery public dataset so we'll need to add a struct which models that result and then a SQL query to query the data. For testing purposes, consider adding a LIMIT clause to your SQL query to reduce how much data it pulls.&lt;/p&gt;

&lt;p&gt;Wiring this up as the first step of our pipeline is as easy as adding the &lt;code&gt;bigqueryio.Query&lt;/code&gt; function which returns one of those &lt;strong&gt;PCollections&lt;/strong&gt; we talked about earlier.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;"reflect"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/io/bigqueryio"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/options/gcpopts"&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CommentRow&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Text&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`bigquery:"text"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;`SELECT text
FROM `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"`bigquery-public-data.hacker_news.comments`"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;`
WHERE time_ts BETWEEN '2013-01-01' AND '2014-01-01'
LIMIT 1000
`&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&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;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPipeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gcpopts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bigqueryio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CommentRow&lt;/span&gt;&lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="n"&gt;bigqueryio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseStandardSQL&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the code above, &lt;code&gt;rows&lt;/code&gt; will be a &lt;code&gt;PCollection&amp;lt;CommentRow&amp;gt;&lt;/code&gt;, effectively a big array where each element is one comment from HackerNews. We're also using the &lt;code&gt;gcpopts&lt;/code&gt; utility which helps grab the GCP Project which BigQuery should bill against; you could just hardcode a string too.&lt;/p&gt;

&lt;h2&gt;
  
  
  StandardSQL vs LegacySQL
&lt;/h2&gt;

&lt;p&gt;Currently, BigQueryIO defaults to LegacySQL in Apache Beam. I do not like using anything "legacy" so the SQL query above uses Standard SQL (note the unusual backtick escaping around the table name) and sets &lt;code&gt;bigqueryio.UseStandardSQL&lt;/code&gt; to enable this. This is a new feature for the Go SDK in Apache Beam 2.23, if your environment is using something older you'll need to upgrade to escape out of LegacySQL mode.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3: Ngram Extraction
&lt;/h1&gt;

&lt;p&gt;Ngrams (&lt;a href="https://en.wikipedia.org/wiki/N-gram" rel="noopener noreferrer"&gt;n-grams&lt;/a&gt;) extract phrases from blocks of text. The text "quick brown fox" contains 3 unigrams: ["quick", "brown", "fox"], 2 bigrams: ["quick brown", "brown fox"] and 1 trigram: ["quick brown fox"].&lt;/p&gt;

&lt;p&gt;We can extract them using a function like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ngram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&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;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;n&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;span class="n"&gt;i&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Since we're dealing with PCollections here, the implementation isn't as easy as calling that function in a for-loop over &lt;code&gt;rows&lt;/code&gt;. Instead, we have to us the &lt;code&gt;ParDo&lt;/code&gt; function, which can distribute this operation across many machines and later aggregate the results. Imagine if this ngram() function was very expensive or slow, it might make sense to have lots of workers extracting ngrams in parallel to get the job done faster. Beam will figure that out for us.&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;ngrams&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParDo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;CommentRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gram&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ngram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gram&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;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This snippet takes the &lt;code&gt;rows&lt;/code&gt; input (specified last) and runs the inline-function on each element (aka each CommentRow). The function also has a reference to an emitter function &lt;code&gt;emit&lt;/code&gt; which is used to collect the results and package them back up into a PCollection. A loop calls emit() on each value returned from the ngram() function above. The result is &lt;code&gt;ngrams&lt;/code&gt; which is an array of each ngram extracted, a &lt;code&gt;PCollection&amp;lt;string&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4: Analytics
&lt;/h1&gt;

&lt;p&gt;Now that we have all the ngrams extracted and floating around in this big magical array it's time to count how many times each value is found in the array. Remember, the previous step just extracted the raw words -- it did not attempt to count/dedup/aggregate them.&lt;/p&gt;

&lt;p&gt;Counting occurrences of things is a pretty common problem with special ways of distributing across multiple workers. Luckily we don't need to deal with any of that, just use the &lt;code&gt;stats.Count&lt;/code&gt; function.&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;counts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngrams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This function returns yet another PCollection, this time containing a key-value pair (aka KV in Beam-lingo) where the key is the ngram string, and the value is an int count... essentially a &lt;code&gt;PCollection&amp;lt;{string, int}&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 5: Outputting
&lt;/h1&gt;

&lt;p&gt;Now that we have the data we want it's time to output it somewhere. For this example, we'll dump it to a text file. To do this we need to make a collection of strings (&lt;code&gt;PCollection&amp;lt;string&amp;gt;&lt;/code&gt;). Another ParDo to the rescue!&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;formatted&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParDo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gram&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s,%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This ParDo uses a slightly different syntax than the previous one. Instead of exposing an &lt;code&gt;emit()&lt;/code&gt; function the inline function just returns a string. We can use this simpler(?) syntax since each element of the input collection &lt;code&gt;counts&lt;/code&gt; only converts to one output element; in our previous use building the ngrams each input string had multiple output values.&lt;/p&gt;

&lt;p&gt;The result here is that &lt;code&gt;formatted&lt;/code&gt; contains a list of strings suitable for printing, &lt;code&gt;PCollection&amp;lt;string&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We then call the &lt;code&gt;textio.Write&lt;/code&gt; function and point it to an output path:&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;textio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/tmp/output.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formatted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;
&lt;h1&gt;
  
  
  Final Code
&lt;/h1&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"flag"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"reflect"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/io/bigqueryio"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/io/textio"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/log"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/options/gcpopts"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/transforms/stats"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/apache/beam/sdks/go/pkg/beam/x/beamx"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// CommentRow models 1 row of HackerNews comments.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;CommentRow&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`bigquery:"text"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;`SELECT text
FROM `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"`bigquery-public-data.hacker_news.comments`"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;`
WHERE time_ts BETWEEN '2013-01-01' AND '2014-01-01'
LIMIT 1000
`&lt;/span&gt;

&lt;span class="c"&gt;// ngram extracts variable sizes of ngrams from a string.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ngram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&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;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;n&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;span class="n"&gt;i&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPipeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gcpopts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Build a PCollection&amp;lt;CommentRow&amp;gt; by querying BigQuery.&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bigqueryio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CommentRow&lt;/span&gt;&lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="n"&gt;bigqueryio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseStandardSQL&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="c"&gt;// Extract unigrams, bigrams, and trigrams from each comment.&lt;/span&gt;
    &lt;span class="c"&gt;// Builds a PCollection&amp;lt;string&amp;gt; where each string is a single&lt;/span&gt;
    &lt;span class="c"&gt;// occurrence of an ngram.&lt;/span&gt;
    &lt;span class="n"&gt;ngrams&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParDo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="n"&gt;CommentRow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emit&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gram&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ngram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gram&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;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Count the occurrence of each ngram.&lt;/span&gt;
    &lt;span class="c"&gt;// Returns a PCollection&amp;lt;{string, count: int}&amp;gt;.&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngrams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Convert each count row into a string suitable for printing.&lt;/span&gt;
    &lt;span class="c"&gt;// Returns a PCollection&amp;lt;string&amp;gt;.&lt;/span&gt;
    &lt;span class="n"&gt;formatted&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParDo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gram&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s,%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gram&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Write each string to a line in the output file.&lt;/span&gt;
    &lt;span class="n"&gt;textio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/tmp/output.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formatted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Now that the pipeline is fully constructed, we execute it.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;beamx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exitf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to execute job: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Running this with a command like &lt;code&gt;$ go run main.go --project=&amp;lt;gcp-project-id&amp;gt;&lt;/code&gt; will kick off a local pipeline and output some ngrams to /tmp/output.txt. To run a more scalable version, perhaps without the &lt;code&gt;LIMIT&lt;/code&gt; SQL clause, check out the "Dataflow" Tab on the &lt;a href="https://beam.apache.org/get-started/quickstart-go/" rel="noopener noreferrer"&gt;Go Quickstart&lt;/a&gt; with the right flags to run on GCP Dataflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7lypzl3jsbhv0y9wu46p.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7lypzl3jsbhv0y9wu46p.PNG" alt="dataflow section with flags highlighted"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy analyzing!&lt;/p&gt;

</description>
      <category>apachebeam</category>
      <category>bigquery</category>
      <category>go</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Using GitLab Managed Kubernetes</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Wed, 29 Jul 2020 23:55:18 +0000</pubDate>
      <link>https://forem.com/bamnet/using-gitlab-managed-kubernetes-5b03</link>
      <guid>https://forem.com/bamnet/using-gitlab-managed-kubernetes-5b03</guid>
      <description>&lt;p&gt;In our last journey, we connected &lt;a href="https://medium.com/@bamnet/connecting-gitlab-to-kubernetes-2334fe53d37f"&gt;Gitlab to a Kubernetes&lt;/a&gt; cluster exploring the &lt;em&gt;"Add Kubernetes Cluster"&lt;/em&gt; button. Now that our cluster is set up, let's put it to use!&lt;/p&gt;

&lt;h1&gt;
  
  
  Running Applications
&lt;/h1&gt;

&lt;p&gt;GitLab's &lt;a href="https://docs.gitlab.com/ee/topics/autodevops/"&gt;Auto DevOps&lt;/a&gt; is too magical for me. I don't understand Helm well enough to use it; and I suspect that my use of Bazel's docker rules will cause problems.  Instead, we'll manually add a deploy step to update our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Add a Helm chart.
&lt;/h2&gt;

&lt;p&gt;Helm charts are essentially yaml templates for Kubernetes resources. They allow you to add variables and set/override them when applying the chart... something you might traditionally do using &lt;code&gt;sed&lt;/code&gt; or another bash trick.&lt;/p&gt;

&lt;p&gt;To create a new Helm chart, run a command like:&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;mkdir &lt;/span&gt;devops&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;devops
helm create myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a myapp/ directory with all of the pieces of a Helm template inside.  You can then preview the YAML output of this template using a command like:&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; &lt;span class="nt"&gt;--dry-run&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt; myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Helm charts can be pretty intimidating. The meat of the template is in &lt;code&gt;templates/deployment.yaml&lt;/code&gt; file. If you want, you can delete all of the fancy templating in this file and replace it with a vanilla yaml for a deployment object.&lt;/p&gt;

&lt;p&gt;For simple apps, tweak things as necessary in your &lt;code&gt;values.yaml&lt;/code&gt;. I kept doing this, and comparing the --dry-run output to a hand-written deployment yaml until I got it looking sort of correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Pulling from GitLab's container registry
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auth
&lt;/h3&gt;

&lt;p&gt;If you're using a private container registry, like Gitlab's, you will need to store some login information in a &lt;strong&gt;secret&lt;/strong&gt; in your Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;To begin, head to your project settings in GitLab and navigate to the Registry section.  Create a Deploy Token and note the username and password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff6v3v0wp6dhtewdgn1m8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ff6v3v0wp6dhtewdgn1m8.png" alt="Deploy Token form" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, follow &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#create-a-secret-by-providing-credentials-on-the-command-line"&gt;this tutorial&lt;/a&gt; to upload that secret to your cluster.  I ended up using a command like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create secret docker-registry regcred \
--docker-server=registry.gitlab.com \
--docker-username=gitlab+deploy-token-123456 \
--docker-password=p@ssw0rdh3r3 \
--docker-email=me@gmail.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NOTE: You probably need to run this command with a &lt;code&gt;--namespace&lt;/code&gt; flag that sets this secret in the same namespace that GitLab picks to run your application.  If it's not set, you'll see errors trying to fetch the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helm Chart Tweak
&lt;/h3&gt;

&lt;p&gt;To use this &lt;code&gt;regcred&lt;/code&gt; secret, add it to the &lt;code&gt;imagePullSecrets&lt;/code&gt; section of your &lt;code&gt;values.yaml&lt;/code&gt; file like this:&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;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;registry.gitlab.com/bamnet/project/image&lt;/span&gt;
  &lt;span class="na"&gt;pullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

&lt;span class="na"&gt;imagePullSecrets&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;regcred&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. .gitlab-ci.yml updates
&lt;/h2&gt;

&lt;p&gt;To apply this Helm chart as part of your CI/CD pipeline, add a job to your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file like the following:&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;deploy_myapp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine/helm:latest&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helm upgrade&lt;/span&gt;
      &lt;span class="s"&gt;--install&lt;/span&gt;
      &lt;span class="s"&gt;--wait&lt;/span&gt;
      &lt;span class="s"&gt;--set image.tag=${CI_COMMIT_SHA}&lt;/span&gt;
      &lt;span class="s"&gt;myapp-${CI_COMMIT_REF_SLUG}&lt;/span&gt;
      &lt;span class="s"&gt;devops/myapp&lt;/span&gt;
  &lt;span class="na"&gt;environment&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;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important part of this entire job is the &lt;code&gt;environment&lt;/code&gt; section. Gitlab only exposes Kubernetes connection information (via environment variables helm and kubectl automatically use) when the deploy stage has an &lt;code&gt;environment&lt;/code&gt; set.  Without this section, you will get errors connecting to your cluster.&lt;/p&gt;

&lt;p&gt;There are 3 parts of the &lt;code&gt;helm upgrade&lt;/code&gt; command worth noting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;--set image.tag=${CI_COMMIT_SHA}&lt;/code&gt; overrides the &lt;code&gt;tag&lt;/code&gt; portion of our deployment.yaml, passing in the git commit hash.  This assumes your containers are tagged with the commit that generates them. If you don't do this, consider a static value like &lt;code&gt;latest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;myapp-${CI_COMMIT_REF_SLUG}&lt;/code&gt; provides the name for this deployment.  If you're deploying from the &lt;code&gt;master&lt;/code&gt; branch, this will be &lt;code&gt;myapp-master&lt;/code&gt;.  This must be unique, so tweak the &lt;code&gt;myapp-&lt;/code&gt; prefix if you have multiple applications.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;devops/myapp&lt;/code&gt; at the end specified the folder where the Helm chart files are located.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pushing this new gitlab-ci file should trigger an automatic deployment to your Kubernetes cluster.  Sit back, relax, and watch the dashboard to see it work.&lt;/p&gt;

&lt;p&gt;If this is your first push, be on the lookout for a new namespace to be created.. probably something like &lt;em&gt;gitlabproject-123456-production&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Locally run &lt;code&gt;helm install --dry-run&lt;/code&gt; to see the planned configuration. If it doesn't look right locally, there is no way GitLab is going to get it right.&lt;/li&gt;
&lt;li&gt;Connect to the Kubernetes Dashboard to see why deployments fail.&lt;/li&gt;
&lt;li&gt;Make sure your image names and tags match between your registry hosting the things and the deployment yaml trying to create them.&lt;/li&gt;
&lt;li&gt;Use the --namespace flag to make sure your registry credentials end up in the right namespace.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Monitoring Applications
&lt;/h1&gt;

&lt;p&gt;GitLab has a one-click install of Prometheus.  I am a sucker for one-click install buttons and wanted to give it a spin monitoring my Go application.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Exporting Metrics
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://opentelemetry.io/"&gt;OpenTelemetry&lt;/a&gt; docs and examples are a good starting point. Prometheus needs and HTTP endpoint to grab the metrics from, a very simple Prometheus exporter looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;initMeter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InstallNewPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prometheus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Panicf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to initialize prometheus exporter %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/metrics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":2222"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Prometheus server running on :2222"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;initMeter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="c"&gt;// Rest of your code here.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Adding Annotations.
&lt;/h2&gt;

&lt;p&gt;Gitlab's one-click Prometheus automatically scrapes metrics from any resource that has certain annotations in place which tell it how to scrape. Add the following to your &lt;code&gt;chart.yaml&lt;/code&gt; file:&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;podAnnotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus.io/scrape&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;prometheus.io/path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/metrics&lt;/span&gt;
  &lt;span class="na"&gt;prometheus.io/port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2222"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it!&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Manually connect to your application and see the exported metrics.  Forward the port using &lt;code&gt;kubectl port-forward -n &amp;lt;gitlab-created-namespace&amp;gt; deployments/myapp-master 2222:2222&lt;/code&gt; and point your browser at &lt;a href="http://localhost:2222"&gt;http://localhost:2222&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Manually connect to Prometheus and use the web UI to see what metrics are being scraped and run queries against them. Forward the port using &lt;code&gt;kubectl port-forward -n gitlab-managed-apps service/prometheus-prometheus-server 9090:80&lt;/code&gt; and point your browser to &lt;a href="http://localhost:9090"&gt;http://localhost:9090&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>gitlab</category>
      <category>devops</category>
    </item>
    <item>
      <title>Dockerfile to Bazel</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Sun, 31 May 2020 21:36:02 +0000</pubDate>
      <link>https://forem.com/bamnet/dockerfile-to-bazel-6jh</link>
      <guid>https://forem.com/bamnet/dockerfile-to-bazel-6jh</guid>
      <description>&lt;p&gt;I recently needed to build a Docker image as part of a large Bazel project I've been working on.  There are a handful of instructions out there how to take a binary built using bazel (like a &lt;code&gt;go_binary&lt;/code&gt;) and convert it along into a container (&lt;code&gt;go_binary&lt;/code&gt; =&amp;gt;&lt;code&gt;go_image&lt;/code&gt; =&amp;gt;&lt;code&gt;container_image&lt;/code&gt;) but it's less clear how to take a basic &lt;code&gt;Dockerfile&lt;/code&gt; with someone else's image and add it to your project. Here's what to do:&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Be prepared to throw out the &lt;code&gt;Dockerfile&lt;/code&gt;.
&lt;/h1&gt;

&lt;p&gt;Bazel's basic &lt;a href="https://github.com/bazelbuild/rules_docker"&gt;Docker rules&lt;/a&gt; don't include any rules that let you just point at an existing Dockerfile and say build. Instead, we have to recreate the &lt;code&gt;Dockerfile&lt;/code&gt; in our &lt;code&gt;BUILD.bazel&lt;/code&gt;. Our existing Dockerfile will be a useful reference so don't throw it out quite yet, but if you've gotten attached to it now would be a good time to say goodbye.&lt;/p&gt;

&lt;p&gt;For our example, let's convert the following Dockerfile which takes an existing Docker image, adds a file to it, and sets a command line flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; bamnet/bqproxy&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; queries.yaml queries.yaml&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["--project=your-project-id"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  2. Add remote dependencies.
&lt;/h1&gt;

&lt;p&gt;Anything that Bazel needs to download remotely need to be specified in a top-level &lt;code&gt;WORKSPACE&lt;/code&gt; config. In this case, we have to add the base image specified in the &lt;code&gt;FROM&lt;/code&gt; line to our &lt;code&gt;WORKSPACE&lt;/code&gt; config since the base image lives on a container registry and not in our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;load("@io_bazel_rules_docker//container:container.bzl", "container_pull")

container_pull(
    name = "bqproxy_latest",
    registry = "index.docker.io",
    repository = "bamnet/bqproxy",
    tag = "latest",
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will expose a &lt;code&gt;@bqproxy_latest//image&lt;/code&gt; item that I can reference later. Like other bazel rules, the &lt;code&gt;name&lt;/code&gt; field can contain whatever I want to refer to the image by elsewhere using the &lt;code&gt;@&amp;lt;name&amp;gt;//image&lt;/code&gt; format.&lt;/p&gt;

&lt;p&gt;Note that my existing Dockerfile didn't specify the registry - it defaulted to &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt; automatically. Bazel doesn't like to make assumptions like this, you need to specify a registry path.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;registry&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker Hub&lt;/td&gt;
&lt;td&gt;index.docker.io&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Container Registry&lt;/td&gt;
&lt;td&gt;gcr.io&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gitlab Container Registry&lt;/td&gt;
&lt;td&gt;registry.gitlab.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Github Packages&lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/td&gt;
&lt;td&gt;docker.pkg.github.com&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  3. Rebuild your &lt;code&gt;Dockerfile&lt;/code&gt; as a &lt;code&gt;container_image&lt;/code&gt;.
&lt;/h1&gt;

&lt;p&gt;Bazel uses the &lt;code&gt;container_image&lt;/code&gt; rule to build Docker images. Add one to the appropriate &lt;code&gt;BUILD.bazel&lt;/code&gt; file for your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;load("@io_bazel_rules_docker//container:container.bzl", "container_image")

container_image(
    name = "my_image",
    // TODO: Figure this out.
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To replicate our earlier Dockerfile we need to set several options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;base&lt;/code&gt; corresponds to the &lt;code&gt;FROM&lt;/code&gt; line.  Specify the &lt;code&gt;name&lt;/code&gt; from the &lt;code&gt;container_pull&lt;/code&gt; rule added to the &lt;code&gt;WORKSPACE&lt;/code&gt; file.  In this example, &lt;code&gt;@bqproxy_latest//image&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmd&lt;/code&gt; specifies command-line options, it can take a string or an array.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;files&lt;/code&gt; specifies files which should be copied into the image. All files will be added to the current working directory. &lt;em&gt;You don't have the ability to set the destination for files being imported like you can using a Docker &lt;code&gt;COPY&lt;/code&gt; config.&lt;/em&gt; Instead, you can use &lt;code&gt;workdir&lt;/code&gt; to globally change the working directory or use a &lt;code&gt;symlink&lt;/code&gt; rule to map files into different locations of the file system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href="https://github.com/bazelbuild/rules_docker#container_image-1"&gt;container_image&lt;/a&gt; docs for the full set of options.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Test it out!
&lt;/h1&gt;

&lt;p&gt;Instead of running &lt;code&gt;docker build .&lt;/code&gt; to build a local docker image, use &lt;code&gt;bazel run :my_image&lt;/code&gt; (substitute the container_image name for my_image).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bazel run :my_image
INFO: Build completed successfully, 1 total action
Loaded image ID: sha256:b5da9bf97f1cdae8c66a775344d3c5ec99002a753973505aa1301bfb0f0b2649
Tagging b5da9bf97f1cdae8c66a775344d3c5ec99002a753973505aa1301bfb0f0b2649 as bazel/server:my_image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will build a Docker image and display it's output path, like &lt;code&gt;bazel/server:my_image&lt;/code&gt; which you can run just like any other docker image.  I used &lt;code&gt;docker run -ti --rm bazel/server:my_image&lt;/code&gt; but you may need to pass additional args.&lt;/p&gt;




&lt;p&gt;Our final conversion looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Dockerfile&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; bamnet/bqproxy&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; queries.yaml queries.yaml&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["--project=your-project-id"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;turned into&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target WORKSPACE&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;load("@io_bazel_rules_docker//container:container.bzl", "container_pull")

container_pull(
    name = "bqproxy_latest",
    registry = "index.docker.io",
    repository = "bamnet/bqproxy",
    tag = "latest",
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Target BUILD.bazel&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;load("@io_bazel_rules_docker//container:container.bzl", "container_image")

container_image(
    name = "my_image",
    base = "@bqproxy_latest//image",
    cmd = ["--project=your-project-id"],
    files = [
        "//server:queries.yaml",
    ],
    workdir = "/",
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;As of May 2020, Github Packages requires authentication even for public packages. In order to use them in your Bazel project you'll need to &lt;a href="https://github.com/bazelbuild/rules_docker#authentication"&gt;configure authentication&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>bazel</category>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
    </item>
    <item>
      <title>Dynamic nginx config on Cloud Run</title>
      <dc:creator>Brian Michalski</dc:creator>
      <pubDate>Fri, 06 Dec 2019 04:08:43 +0000</pubDate>
      <link>https://forem.com/bamnet/dynamic-nginx-config-on-cloud-run-hpc</link>
      <guid>https://forem.com/bamnet/dynamic-nginx-config-on-cloud-run-hpc</guid>
      <description>&lt;p&gt;Google &lt;a href="https://cloud.google.com/run/" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; has quickly become one of my favorite cloud products. Not only is it super simple to setup (*cough* k8s *cough*), but it also has a generous free tier and lets you run nearly any stateless container.  It also supports custom domains, with a small CNAME tweak you can start serving foo.your-domain.com over HTTPS without any extra work.  Where have you been all my life?&lt;/p&gt;

&lt;p&gt;This got me thinking -- could I just run an nginx container on Cloud Run and, with a reverse proxy config, glue together a bunch of microservices and static resources under a single domain? I could effectively use Cloud Run as a frontend server / poor-man's load balancer &lt;em&gt;(without the actual balancing features)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I wanted to avoid baking or hardcoding my &lt;code&gt;nginx.conf&lt;/code&gt; file in each Docker image.    Launching a new microservice on my domain should require a simple config change, not building, pushing, and deploying a whole new Docker image. To do that, we have to use the 1 editable thing Cloud Run provides: environment variables.&lt;/p&gt;

&lt;p&gt;With some clever bash-foo, we can build a &lt;code&gt;Dockerfile&lt;/code&gt; which takes a &lt;code&gt;$CONFIG&lt;/code&gt; environment variable and injects it into to a file before starting nginx. We can change the CONFIG variable as many times as we want without needing to rebuild a new Docker image each time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; /etc/nginx/conf.d/default.conf
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; site.template /etc/nginx/conf.d/site.template&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; /bin/bash -c "envsubst &amp;lt; /etc/nginx/conf.d/site.template &amp;gt; /etc/nginx/conf.d/site.conf &amp;amp;&amp;amp; exec nginx -g 'daemon off;'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[&lt;a href="https://github.com/bamnet/cloud-run-fun/blob/master/nginx/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt; on Github]&lt;/p&gt;




&lt;p&gt;With that in place, configuring our nginx instance is as easy as copying an nginx config blob into the Cloud Run UI.  The UI doesn't support multi-line strings so everything gets mashed into 1 line, but nginx doesn't care about line breaks when it's reading the config.&lt;/p&gt;

&lt;p&gt;As an example, you can run a very simple reverse proxy by pasting this into a CONFIG variable:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6lwyh9ef6sn3ouphhp54.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6lwyh9ef6sn3ouphhp54.png" alt="Cloud Run Config section"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location / {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_buffering off;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_pass http://www.google.com;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or something a bit fancier which mixes in some Cloud Storage resources (a great way to expose them over HTTPS on a custom domain on the cheap):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location /static/ {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_buffering off;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_pass https://storage.googleapis.com/cloud-run-fun-static-test/;
}

location / {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_buffering off;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_pass http://www.google.com;
}

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

&lt;/div&gt;



&lt;p&gt;In the Cloud Run UI, this looks a little bit like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6coqp51bj9b9i0loy6cq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6coqp51bj9b9i0loy6cq.png" alt="Cloud Run Details section"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you want to quickly try this out yourself, you can deploy my nginx image from &lt;code&gt;gcr.io/cloud-run-fun/nginx:latest&lt;/code&gt; or jump right to the Console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://console.cloud.google.com/run/create?image=gcr.io%2Fcloud-run-fun%2Fnginx:latest" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdeploy.cloud.run%2Fbutton.svg" alt="Run on Google Cloud"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full code @ &lt;a href="https://github.com/bamnet/cloud-run-fun" rel="noopener noreferrer"&gt;https://github.com/bamnet/cloud-run-fun&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>googlecloudrun</category>
      <category>googlecloud</category>
      <category>nginx</category>
      <category>cloudrun</category>
    </item>
  </channel>
</rss>
