<?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>DEV Community: Katie McLaughlin</title>
    <description>The latest articles on DEV Community by Katie McLaughlin (@glasnt).</description>
    <link>https://dev.to/glasnt</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F167207%2F078c6167-7b77-47d5-bc3a-eed0cf2e89e2.jpeg</url>
      <title>DEV Community: Katie McLaughlin</title>
      <link>https://dev.to/glasnt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/glasnt"/>
    <language>en</language>
    <item>
      <title>Step up to the Sheets: AI Eval Export and Illustrating Data</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Tue, 01 Sep 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/googleai/step-up-to-the-sheets-ai-eval-export-and-illustrating-data-bak</link>
      <guid>https://dev.to/googleai/step-up-to-the-sheets-ai-eval-export-and-illustrating-data-bak</guid>
      <description>&lt;p&gt;Welcome to part three of our series about designing, analyzing and visualizing AI Evals! As with the previous entries you are more than welcome to simply read over my exploration or for those of you looking for extra credit, feel free to play along at home.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This blog series contains AI-generated diagrams alongside actual screenshots and hand-drawn edits of both. AI also assisted in minor copy editing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Implementation prerequisites
&lt;/h2&gt;

&lt;p&gt;If you intend to (re)implement the walkthrough, all you need to do right now is complete Part 1 (&lt;a href="https://dev.to/googleai/designing-ai-evals-clarity-now-and-visualization-next-4eii"&gt;Designing AI Evals: Clarity Now and Visualization Next&lt;/a&gt;) and then install &lt;code&gt;pandas&lt;/code&gt;; Part 2 (&lt;a href="https://dev.to/googleai/ai-evals-at-a-glance-heatmaps-for-stakeholders-2mki"&gt;AI Evals at a Glance: Heatmaps for Stakeholders&lt;/a&gt;) is illustrative and will give you a deeper understanding going forward both of AI evals and why the following may be useful but it’s more elective than anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the story at hand
&lt;/h2&gt;

&lt;p&gt;While engineering teams might evaluate agents using test automations, non-technical stakeholders (product managers, business leads, or executive sponsors) often use spreadsheets as their lingua franca and as such it would make sense to speak to them in a way they are almost certain to understand and which enables you all to collaboratively make visualizations to further get out the word.&lt;/p&gt;

&lt;p&gt;To make this a reality, today we’ll use a &lt;code&gt;pandas&lt;/code&gt; pipeline that transforms raw Inspect AI evaluation logs into a rich CSV optimized for Google Sheets, allowing you or anyone on your team with a little spreadsheet knowledge to make boardroom-ready visuals.&lt;/p&gt;

&lt;p&gt;Drawing again from previous blog entries, the evals we are working on have three dimensions: model x skill x sample [and epoch] that cross to form the following matrix:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosimtffx3o7lgyyj1jc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosimtffx3o7lgyyj1jc2.png" alt="A 3D grid matrix visualizing evaluation conditions: Models on the vertical axis, Skill Conditions on the depth axis, and Samples on the horizontal axis. A callout box shows a grader model evaluating four facts, resulting in a score on a meter." width="800" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/googleai/ai-evals-at-a-glance-heatmaps-for-stakeholders-2mki"&gt;Part 2&lt;/a&gt; we created a simplified heatmap reflecting a planar slice of this space as reduced over the sample dimension. While we will ultimately do something similar in a future blog post using Data Studio, for the time being we actually want to keep all 32 combinations separate and pull more of their underlying stats from the &lt;code&gt;inspect eval&lt;/code&gt; files and derive further information using &lt;code&gt;pandas&lt;/code&gt;. This information will be exported as a flat csv file we can then import and view in Google Sheets.&lt;/p&gt;

&lt;h2&gt;
  
  
  The extractor &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/tocsv.py" rel="noopener noreferrer"&gt;&lt;code&gt;tocsv.py&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This script is used to parse the eval logs using &lt;code&gt;Inspect AI&lt;/code&gt;’s python &lt;code&gt;pandas&lt;/code&gt; interface. The script is relatively self documenting, and you can &lt;a href="https://pandas.pydata.org/docs/user_guide/10min.html" rel="noopener noreferrer"&gt;read up more on pandas&lt;/a&gt; and &lt;a href="https://pandas.pydata.org/docs/user_guide/io.html#" rel="noopener noreferrer"&gt;pandas’ csv IO&lt;/a&gt; if you are unfamiliar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Heavy lifting in Python: Custom telemetry columns
&lt;/h3&gt;

&lt;p&gt;While most of the script deals with mapping scalar metrics the like of which we’ve already discussed, there are two that are purely logical/boolean. They represent specific statistical cases that we will allow end users to partition the data set over for analysis later. These boolean conditions are derived and since we want to pare away excess information and therefore not supply the full eval files to Google Sheets, &lt;code&gt;tocsv.py&lt;/code&gt; does the heavy lifting upstream during log ingestion.&lt;/p&gt;

&lt;p&gt;It pre-computes two key boolean columns to simplify downstream dashboard filtering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;completed&lt;/code&gt;&lt;/strong&gt;: Flags whether a sample finished cleanly (no API quota 429 errors, timeout truncations, or container crashes) with a valid score, cleanly isolating model capability from infrastructure noise.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;is_baseline_or_has_activated_skill&lt;/code&gt;&lt;/strong&gt;: Distinguishes &lt;strong&gt;Baseline control cohorts&lt;/strong&gt; (always &lt;code&gt;TRUE&lt;/code&gt;) and &lt;strong&gt;Active Skill interventions&lt;/strong&gt; (&lt;code&gt;TRUE&lt;/code&gt; when the skill tool was actually “activated” which in &lt;code&gt;inspect&lt;/code&gt; means uptaken) from &lt;strong&gt;Dormant runs&lt;/strong&gt; (&lt;code&gt;FALSE&lt;/code&gt;, where skills were available but never activated).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Google Sheets or Data Studio non-technical stakeholders can simply filter/toggle these pre-computed boolean keys to isolate clean, active performance (or otherwise) instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exporting to Google Sheets
&lt;/h2&gt;

&lt;p&gt;In order to execute the pipeline I called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 tocsv.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output file &lt;code&gt;data_mega_export.csv&lt;/code&gt; is a standard comma-separated values file where each cell is surrounded by commas. You can preview the raw and unformatted contents in a standard text editor. While most of this is normal spreadsheet data such as strings and integers, you will also see fairly complicated Google Sheets formulas resulting from interpolating eval metrics into formula templates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvzqm5o95s8ac43ups37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvzqm5o95s8ac43ups37.png" alt="A screenshot showing raw text lines of a CSV export, highlighting a complex Google Sheets formula involving SPARKLINE and PERCENTILE functions used to generate dynamic bar charts in the sheet." width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above formula for instance will be transformed into a bar graph normalized against the range of values in the same column as its source data and tiered into colors by tertile. &lt;/p&gt;

&lt;p&gt;How do you take this text and get it to show its stuff? By opening &lt;a href="https://sheets.new" rel="noopener noreferrer"&gt;sheets.new&lt;/a&gt; of course, and importing &lt;code&gt;data_mega_export.csv&lt;/code&gt;. When presented with the import dialog check &lt;strong&gt;Convert text to numbers, dates, and formulas&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2m83vhwiinwg0cvcbqg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr2m83vhwiinwg0cvcbqg.png" alt="A screenshot of the Google Sheets Import file dialog for data\_mega\_export.csv, with the checkbox " width="799" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On clicking &lt;strong&gt;Import data&lt;/strong&gt; Google Sheets will instantly display the standard cells and render active, color-coded visual sparklines directly inside data cells!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8y9jhes042pv9emzwddo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8y9jhes042pv9emzwddo.png" alt="A screenshot of the imported data in Google Sheets, displaying columns like model, skill\_group, score, and latency, with inline yellow and green sparkline bar charts rendering directly inside the cells." width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What conclusions can you draw from what you see? One obvious one in this example is that the correlation between score and latency (how long an agent takes to answer a question) may not be very strict. But what if you wanted to get something deeper?&lt;/p&gt;

&lt;p&gt;Well, since this is a spreadsheet, obviously you can make pivot tables, and custom functions or sort on multiple keys… you know, the whole accountant’s playbook. Let’s say though that you want custom views into the data rather than to permanently mutate it. How would you go about doing that? &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkanon14o38dtgmkh2os.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkanon14o38dtgmkh2os.png" alt="A screenshot of the Google Sheets top menu under " width="788" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google Sheets, unlike &lt;code&gt;inspect view&lt;/code&gt; and &lt;code&gt;inspect viz&lt;/code&gt; heatmaps, allows you to dynamically sort AND filter on more than one key. Not only that but you can sort on ANY key, so I elected to do so with the previously unavailable &lt;code&gt;score&lt;/code&gt; and &lt;code&gt;latency&lt;/code&gt; fields. As shown in the image above, use &lt;strong&gt;Data&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create filter view&lt;/strong&gt; which presents you with a new set of controls at the top of the main columns.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Filter views do not mutate or change the underlying contents of a sheet; Instead they are (optionally named and shareable) configurations for restricting what rows are displayed and in what order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbjv901ugvtpwkkyroui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbjv901ugvtpwkkyroui.png" alt="A screenshot showing an active " width="799" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If, as in the picture, you click the &lt;code&gt;cost&lt;/code&gt; column &amp;gt; &lt;strong&gt;Filter by condition&lt;/strong&gt;, select the  &lt;strong&gt;Less than&lt;/strong&gt; option and fill the comparison with 0.01, you can narrow the displayed rows to only those with costs less than a cent. This can be used (for instance) to only look over the samples that were “less costly” to try to analyze why.&lt;/p&gt;

&lt;p&gt;Filter views (despite their name) also allow you to sort the display of the data. Because the sort operations are cumulative, the “primary” filter needs to be applied last. Thus, after sorting the filter view first by &lt;strong&gt;latency&lt;/strong&gt; and then by &lt;strong&gt;score&lt;/strong&gt; (both ‘Z to A’ aka decreasing) gives us the following: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fddq6a27tij7c9cozbfdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fddq6a27tij7c9cozbfdj.png" alt="A screenshot of " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Optionally you can name and save this filter view to toggle it on and off as well as share it with other collaborators. To learn more about Filter views, you can read &lt;a href="https://support.google.com/docs/answer/3540681?hl=en&amp;amp;co=GENIE.Platform%3DDesktop" rel="noopener noreferrer"&gt;Sort &amp;amp; filter your data&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This ability to sort and filter the display live without changing the underlying information, makes presenting the data and analyzing it during discussions with teammates significantly easier and faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why leave the cradle?
&lt;/h3&gt;

&lt;p&gt;Now, in spite of this tremendous flexibility, sheets have some drawbacks as visualization tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Spreadsheet formulas are cumbersome for nested IF statements and filters unless you define custom macros or JavaScript functions leading to code that's harder and harder to keep connected in one's mental model.
&lt;/li&gt;
&lt;li&gt;Flattened eval runs need to be inspected under multiple filter views to begin to understand complex trends or correlations across all of them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, how can we take this to the next level and circumvent those issues? What about an environment, which while it still offers you the ability to custom script anything you need, comes with a lot of WYSIWYG controllable visualizations and graphs, enabling you, without code, to bring data to an even more vibrant life? Better yet, what if that environment comes with built in visualizations for 3 dimensions or more and has broad compatibility with multiple data source types?&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 4&lt;/strong&gt;, we’ll connect this Google Sheet directly to Data Studio to build a codeless executive summary dashboard and be able to quickly and intuitively display our findings! &lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>datascience</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI Evals at a Glance: Heatmaps for Stakeholders</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Tue, 25 Aug 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/googleai/ai-evals-at-a-glance-heatmaps-for-stakeholders-2mki</link>
      <guid>https://dev.to/googleai/ai-evals-at-a-glance-heatmaps-for-stakeholders-2mki</guid>
      <description>&lt;h2&gt;
  
  
  Visualizing AI evals with Inspect Viz
&lt;/h2&gt;

&lt;p&gt;Welcome back to our blog series on running, analyzing and visualizing AI evals. &lt;a href="https://dev.to/googleai/designing-ai-evals-clarity-now-and-visualization-next-4eii"&gt;Last time&lt;/a&gt; we discussed how to design and run evals for analysis and visualization using &lt;code&gt;inspect eval&lt;/code&gt; and &lt;code&gt;inspect view&lt;/code&gt;. Many of these methods, such as rearranging the dashboard columns and sorting on them to determine model vs model and skill vs no-skill differences in metrics, can give you a broad overview of the field. While this can inspire further and deeper inquiry, it runs into issues with how predictive it is and how to communicate findings to other people. Imagine the following:&lt;/p&gt;

&lt;h3&gt;
  
  
  The 40-log gridlock
&lt;/h3&gt;

&lt;p&gt;Imagine a data science lead needing to present a performance breakdown of four LLM candidates across ten internal tools during a high-stakes, time-crunched live meeting. Reordering and filtering the evals across multiple dimensions is hard to read on a presentation screen and requires doing mental math with an audience: riveting stuff that they definitely won’t fall asleep during of course.&lt;/p&gt;

&lt;p&gt;What if instead you had a tool that allowed you to easily automate rendering a comparison of the different configurations? What if lining them up by model or by skill showed clear and legible patterns?&lt;/p&gt;

&lt;p&gt;Well, let me introduce you to &lt;strong&gt;heatmaps&lt;/strong&gt;! A heatmap is a 2D visual matrix where color intensity represents numerical magnitude—in our case, quadratic curved accuracy scores. By leveraging the human eye's natural ability to distinguish color gradients, heatmaps instantly reveal performance spectrums at a single glance. This makes trends across dependent and independent variables clear, intuitive, and highly legible.&lt;/p&gt;

&lt;p&gt;While you can simply read along and learn about using &lt;code&gt;inspect viz&lt;/code&gt; to generate heatmaps for inspect AI evals, if you intend to follow along and recreate the charts, please start by replicating the steps in the &lt;a href="https://dev.to/googleai/designing-ai-evals-clarity-now-and-visualization-next-4eii"&gt;first entry&lt;/a&gt; and come back afterward.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This blog series contains AI-generated diagrams alongside actual screenshots and hand-drawn edits of both. AI also assisted in minor copy editing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Follow-along requirements
&lt;/h2&gt;

&lt;p&gt;For those playing along at home, in order to perform the following, you will need the &lt;code&gt;inspect_viz&lt;/code&gt; library installed in your environment (alongside &lt;code&gt;inspect_ai&lt;/code&gt; and &lt;code&gt;pandas&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;From there, take a look at the &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/inspect_viz_heatmap.py" rel="noopener noreferrer"&gt;heatmap script&lt;/a&gt;. While there are some intricacies to the script (such as establishing its CLI API) the script mainly wraps &lt;code&gt;scores_heatmap(viz_data, orientation="horizontal", ...)&lt;/code&gt; and marshals our data to assemble a heatmap of it.&lt;/p&gt;

&lt;p&gt;If you run the script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 inspect_viz_heatmap.py logs &lt;span class="nt"&gt;-o&lt;/span&gt; heatmap.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you will get a visual that looks like the following.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwahzh78quzwkcq5aer3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwahzh78quzwkcq5aer3t.png" alt="A heatmap matrix with model names on the y-axis and skill names on the x-axis, using color intensity from purple to yellow to represent accuracy scores." width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This heatmap has two axes, one is the &lt;strong&gt;model&lt;/strong&gt; (vertical) which allows you to visually break down your analysis to just skill inclusions for the same model, the other is the &lt;strong&gt;skills&lt;/strong&gt; (horizontal) and going down the row allows you to demonstrate how changes to the model affect the evaluation.&lt;/p&gt;

&lt;p&gt;Stakeholders (like investors or your boss) can now spot skill gains and model ceilings at a glance without reading line-by-line log values. Some quick takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Top-performing configurations (such as 3.6-flash + &lt;code&gt;gemini_api_skill&lt;/code&gt; at &lt;strong&gt;1.0&lt;/strong&gt;) stand out in high-intensity shades
&lt;/li&gt;
&lt;li&gt;Within the aggregated 2D heatmap view of this dataset, &lt;code&gt;3.6-flash&lt;/code&gt; trended higher in score on average across the evaluated skill configurations compared to &lt;code&gt;3.5-flash-lite&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Skills tended to improve scores, all other things held equal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwvjbu92rzpou4aavxzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbwvjbu92rzpou4aavxzu.png" alt="A 3D grid matrix visualizing eval conditions: Models on the vertical axis, Skill Conditions on the depth axis, and Samples on the horizontal axis." width="800" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caption&lt;/strong&gt;: The &lt;code&gt;inspect viz&lt;/code&gt; heatmap projects our full Evaluation Matrix down to a 2D plane by collapsing individual task samples and epochs into aggregate accuracy cells along the Model and Skill axes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cognitive overhead and segue to BI analytics
&lt;/h3&gt;

&lt;p&gt;While matrix scoreboards provide quick visibility into accuracy differences across models, raw heatmaps suffer from key limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2D Constraint&lt;/strong&gt;: Heatmaps map only two axes to a single color intensity (in our case Accuracy).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing Business Context&lt;/strong&gt;: A single heatmap cannot simultaneously show token costs, system latency, or operational reliability.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hidden Variance&lt;/strong&gt;: Collapsing samples for a given configuration removes the information necessary to determine if a result is consistent or wildly erratic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To bridge CLI logs with interactive, multidimensional and in-depth reporting, we need a pipeline that exports the raw telemetry metrics into spreadsheets and BI dashboards.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt;, we’ll  build a python script to convert output data for processing first in Google Sheets and ultimately Data Studio!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>datascience</category>
      <category>python</category>
    </item>
    <item>
      <title>Designing AI Evals: Clarity Now and Visualization Next</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Tue, 18 Aug 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/googleai/designing-ai-evals-clarity-now-and-visualization-next-4eii</link>
      <guid>https://dev.to/googleai/designing-ai-evals-clarity-now-and-visualization-next-4eii</guid>
      <description>&lt;h2&gt;
  
  
  AI evals and analysis
&lt;/h2&gt;

&lt;p&gt;Let's say you're testing out new AI tools. Perhaps you implement and run analytics for an Ad Agency and hope to automate deploying your standard event schema, or are a podcast producer automating generating social copy from your newest ep. While modern, newly trained LLMs can likely one-shot a lot of these tasks – this specificity might necessitate wasting tokens and time repeatedly prompting them with the same resources, descriptions and scripts. With this in mind, you investigate tooling whether that be an MCP server, an agent skill or an &lt;a href="https://agent-plugins.org/specification" rel="noopener noreferrer"&gt;agent plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The problem is, how do you know if a skill (whether developed by you or open sourced by someone else) is worth your time or, perhaps more importantly, your tokens and quota? How can one go about designing these (more) objective evaluations of AI tools and, from there, collecting and analyzing relevant metrics? That is exactly the context for using open source eval frameworks, like &lt;a href="https://inspect.aisi.org.uk/" rel="noopener noreferrer"&gt;Inspect AI&lt;/a&gt; and &lt;a href="https://github.com/harbor-framework/harbor" rel="noopener noreferrer"&gt;Harbor&lt;/a&gt; to &lt;a href="https://codelabs.developers.google.com/codelabs/evaluate-agent-skills-using-open-source-frameworks" rel="noopener noreferrer"&gt;evaluate agent skills using open source frameworks&lt;/a&gt;. But how do you &lt;a href="https://codelabs.developers.google.com/codelabs/evaluate-agent-skills-using-open-source-frameworks#4" rel="noopener noreferrer"&gt;"extend the evaluation"&lt;/a&gt;, use visualizations to spot trends and collaboratively explore alternative paths forward using Google Sheets and Data Studio?&lt;/p&gt;

&lt;p&gt;These questions and more are exactly what I’m hoping to demonstrate for you in this series!&lt;/p&gt;

&lt;p&gt;While you are more than welcome to simply read about how and why I conducted my own investigation, you can alternatively follow my lead and &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#reproduction-guide" rel="noopener noreferrer"&gt;run the benchmark scripts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, if you’re hoping to play along, take a moment to &lt;a href="https://codelabs.developers.google.com/codelabs/evaluate-agent-skills-using-open-source-frameworks" rel="noopener noreferrer"&gt;complete the aforementioned codelab&lt;/a&gt; and come back when you’re done. Don’t worry, we’ll still be here when you get back!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This blog series contains AI-generated diagrams&lt;br&gt;
alongside actual screenshots and hand-drawn edits of&lt;br&gt;
both. AI also assisted in minor copy editing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Level up your evals
&lt;/h2&gt;

&lt;p&gt;In the codelab, we learned how to run evals with Gemini CLI, Inspect and Inspect SWE in an isolated Docker Sandbox to understand how well each skill aids the agent in answering the same question.&lt;/p&gt;

&lt;p&gt;For those playing along at home, please additionally install &lt;code&gt;inspect view&lt;/code&gt; now before you need it to run the evals below; be prepared to have to wait for a couple minutes for evals to finish depending on your machine and quota usage.&lt;/p&gt;

&lt;p&gt;Regarding our new &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/tree/main/agents/inspect-agent-skills-eval#downloading-the-demo-giget" rel="noopener noreferrer"&gt;source files&lt;/a&gt; while they are heavily commented and hopefully written in a self describing way, I’ll explain further subsequently.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on models
&lt;/h3&gt;

&lt;p&gt;For the purpose of my investigation demo, I used three different models: &lt;code&gt;google/gemini-3.5-flash-lite&lt;/code&gt; and &lt;code&gt;google/gemini-3.6-flash&lt;/code&gt; as "solvers" (the models under evaluation), and &lt;code&gt;google/gemini-3.1-flash-lite&lt;/code&gt; as a "grader" (the model rating the runs). The three of them (and models more broadly) differ in many ways but more specifically on &lt;a href="https://ai.google.dev/gemini-api/docs/models" rel="noopener noreferrer"&gt;problem solving ability, speed and cost&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We intentionally offloaded grading for this demo to a previous-generation model because by changing rubric criteria to strict binary decisions and applying a reduction programmatically, it delivers sufficiently robust evaluations without burning through solver quota. For a &lt;em&gt;production&lt;/em&gt; evaluation system, consider investigating using newer and more capable models as graders because they will likely have narrower confidence intervals.&lt;/p&gt;

&lt;p&gt;For a full technical breakdown of this decoupled grader architecture and to find out how you could substitute in your own choice of models, see the README section on &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#3-decoupled-grader--multidimensional-rubrics" rel="noopener noreferrer"&gt;Decoupled Grader &amp;amp; Multidimensional Rubrics&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the eval
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reproducibility &amp;amp; Local Setup Note&lt;/strong&gt;: If you are following along locally, clone the domain skill definitions into &lt;code&gt;google-skills/&lt;/code&gt; before running benchmark sweeps:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/google/skills.git google-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;All evaluations in this series were benchmarked on &lt;strong&gt;Python 3.13&lt;/strong&gt; using &lt;code&gt;inspect-ai&lt;/code&gt; (&lt;code&gt;v0.3.247&lt;/code&gt;), &lt;code&gt;inspect-swe&lt;/code&gt; (&lt;code&gt;v0.2.66&lt;/code&gt;), &lt;code&gt;inspect-viz&lt;/code&gt; (&lt;code&gt;v0.4.1&lt;/code&gt;), and &lt;code&gt;pandas&lt;/code&gt; (&lt;code&gt;v3.0.3&lt;/code&gt;). If upstream PyPI releases introduce breaking changes, check the README's &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#environment--dependencies" rel="noopener noreferrer"&gt;Environment &amp;amp; Dependencies specification&lt;/a&gt; for exact version pins and instructions on how to reproduce the configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this investigation I used a &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/skills-eval.py" rel="noopener noreferrer"&gt;new eval script&lt;/a&gt;; while the specifics of the script are important for anyone who hopes to make their own evals or run the code as we go, if you're primarily interested in analysis and visualization, feel free to skip to the next section.&lt;/p&gt;

&lt;p&gt;The original script ran a small batch of tests on a local machine. While this version can be and was run on a local machine, it prioritizes three architectural dimensions needed for running automated tests as part of a larger scale development flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;External Configs (providing the evals and solver system prompt as external files for separation of concerns).
&lt;/li&gt;
&lt;li&gt;Quota Management (for the GenAI API and package management).
&lt;/li&gt;
&lt;li&gt;Multidimensional Evaluations (expanding to arbitrary fact counts for graders).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a complete technical breakdown of these architectural pillars—including external configuration schemas (&lt;code&gt;questions.json&lt;/code&gt;, &lt;code&gt;thrifty_system_prompt.txt&lt;/code&gt;), solver rate-limiting defenses (&lt;code&gt;version="0.51.0"&lt;/code&gt; pinning), grader quota decoupling, and score curving math formulas see the README's &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#1-external-configurations" rel="noopener noreferrer"&gt;Evaluation Pipeline Architecture &amp;amp; Technical Reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the eval
&lt;/h2&gt;

&lt;p&gt;These commands create a matrix of evals subject to model x skill condition x sample x epoch. For a quick visual (that I’ll repeatedly reference throughout this), take a look below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosimtffx3o7lgyyj1jc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosimtffx3o7lgyyj1jc2.png" alt="A 3D grid matrix visualizing eval conditions: Models on the vertical axis, Skill Conditions on the depth axis, and Samples on the horizontal axis. A callout box shows a grader model evaluating four facts, resulting in a score on a meter." width="800" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caption&lt;/strong&gt;: Visualization of the eval matrix that will be scored and compared. In Inspect AI, rubric elements, represented here as Facts, are graded Correct (C) or Incorrect (I).&lt;/p&gt;

&lt;p&gt;With all that said, it’s finally time for the rubber to meet the road. I ran our evals with the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;inspect &lt;span class="nb"&gt;eval &lt;/span&gt;skills-eval.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; google/gemini-3.5-flash-lite,google/gemini-3.6-flash &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--time-limit&lt;/span&gt; 300 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--epochs&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-tasks&lt;/span&gt; 4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-T&lt;/span&gt; &lt;span class="nv"&gt;web_access&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command runs a 4-way parallel sweep across models and skill conditions while enforcing a 300-second task timeout and disabling web search to minimize token spend. See the README for a complete &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#cli-parameter-reference" rel="noopener noreferrer"&gt;CLI Parameter Reference&lt;/a&gt; and details on &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#1-external-configurations" rel="noopener noreferrer"&gt;External Configuration Template Mechanics&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run and wait
&lt;/h3&gt;

&lt;p&gt;If you intend to and haven't done so already, &lt;strong&gt;run the command line&lt;/strong&gt;. If you're interested in how to read over and interact with the Terminal User Interface, please refer to the &lt;a href="https://codelabs.developers.google.com/codelabs/evaluate-agent-skills-using-open-source-frameworks" rel="noopener noreferrer"&gt;previous codelab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Watch the running command line long enough to determine it hasn’t crashed or hit an obvious error and then maybe take a moment to make some food, grab some water or take a walk around the block. Enjoy the small things in life, okay? Worst case, you get a 20 minute walk out of it, not so bad eh!?&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing the evals
&lt;/h2&gt;

&lt;p&gt;With that said and done, reading the terminal output is only the most basic way of understanding the evals. Now that you have the raw eval logs, what do you need to do to begin to use these analytically and, more importantly, persuasively?&lt;/p&gt;

&lt;p&gt;To start off, I simply ran&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;inspect view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and clicked the link to open the browser to the GUI.&lt;/p&gt;

&lt;p&gt;For step-by-step diagnostic trace analysis in the browser GUI, see the README section on &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#local-diagnostic-trace-analysis-inspect-view" rel="noopener noreferrer"&gt;Local Diagnostic Trace Analysis (inspect view)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mine came out like this (after a little bit of column drag-and-drop):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8mxn1iszjshhw6f1w7ki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8mxn1iszjshhw6f1w7ki.png" alt="A table showing the results of AI evaluation runs, with columns for status, task name, model used, score, token count, and duration." width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While we covered the rudiments of looking over the inspect view GUI in &lt;a href="https://codelabs.developers.google.com/codelabs/evaluate-agent-skills-using-open-source-frameworks" rel="noopener noreferrer"&gt;Evaluate agent skills using open source frameworks&lt;/a&gt;, there is significantly more that can be done with it. Speaking of which, the &lt;strong&gt;TASK ARGS&lt;/strong&gt; column (not pictured) serves as a quick reference for what arguments were passed to a task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Macro view: Comparing runs
&lt;/h3&gt;

&lt;p&gt;While LLM’s are inherently stochastic (and given floating point hardware, nondeterministic), when running evals we are trying to use sampling to characterize the average observable metrics (e.g. correctness, latency and token usage) which our particular configuration model x skill scores on relevant questions. As such, the eval tasks were set up to characterize how a change to an independent variable (model or skill) affect dependent variables (the aforementioned metrics).&lt;/p&gt;

&lt;h4&gt;
  
  
  A. Comparing models head to head
&lt;/h4&gt;

&lt;p&gt;Grouping evaluation runs by model in &lt;code&gt;inspect view&lt;/code&gt; allows for a direct "eyeball" inspection of how skill inclusion alters accuracy across baseline controls—&lt;strong&gt;given a specific model, what is the impact of adding a skill?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have added some annotations to make metric change from baseline to skilled a little more apparent:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvssywiosux6j1i7inxhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvssywiosux6j1i7inxhe.png" alt="A table of AI evaluation results similar to the previous one, but with red up-arrows next to 'SCORE' values to indicate improvements when using skills compared to baseline." width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above shows that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In all cases, the addition of a skill improved or tied scores relative to the same model’s baseline.
&lt;/li&gt;
&lt;li&gt;All of these cases &lt;strong&gt;also&lt;/strong&gt; increased duration &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another simple measurement that can be done is to look at the &lt;code&gt;TOKENS&lt;/code&gt; column where gemini-api actually led to a decrease in usage relative to its baseline in &lt;code&gt;3.6-flash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While the general relationship between tokens and score is difficult to parse visually, one notable result: for 3 out of 4 task x model configurations, gemini-3.6-flash used more tokens than the comparable 3.5-flash-lite run.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Comparing skill conditions (skill vs. baseline)
&lt;/h3&gt;

&lt;p&gt;Now to analyze the inverse; in more concrete terms: &lt;strong&gt;given a particular skill, what is the effect of changing the underlying model?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo04b8da7cpec0hntywy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgo04b8da7cpec0hntywy.png" alt="A table of AI evaluation results comparing performance across models for a given skill condition, with annotations indicating score differences." width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What was the effect of the change from &lt;code&gt;3.5-flash-lite&lt;/code&gt; to &lt;code&gt;3.6-flash&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;For 3 out of 4 of these, the changes between the two models amounted to an up to 45% increase in accuracy. For whatever reason, however, changing the &lt;code&gt;gcloud&lt;/code&gt; skill from using one model to another led to a slight decrease.&lt;/p&gt;

&lt;p&gt;While these are both valuable findings, they warrant further questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are these representative samples?
&lt;/li&gt;
&lt;li&gt;If these findings are repeatable, how can we characterize the relationship between the existing metrics?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first of those, if you want to make more representative samples, know that you should conduct further research using more questions, samples and epochs and compare those metrics with these. For the sake of this blog series though, I’ll leave that for you to do.&lt;/p&gt;

&lt;p&gt;For the second though, we can and will endeavor to do so. That said, it might help in designing follow up analysis to think a little bit about common patterns encountered during evals. This is especially true for those running evals themselves (possibly on different skills or rubrics); you are likely to encounter very different metrics than mine, and thus I’ll lay out some common patterns and follow up actions to investigate them further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mental models for eval comparison (skill vs. baseline)
&lt;/h3&gt;

&lt;p&gt;When analyzing evaluation runs, comparing skilled execution against baseline controls typically maps to five distinct diagnostic outcomes—ranging from &lt;strong&gt;High-Efficiency Capability Lift&lt;/strong&gt; (best) to &lt;strong&gt;Context Overload &amp;amp; Skill Regression&lt;/strong&gt; (worst).&lt;/p&gt;

&lt;p&gt;For a complete breakdown of this diagnostic taxonomy and actionable audit steps for each outcome, see the README breakdown of &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#diagnostic-mental-models-for-eval-comparison-skill-vs-baseline" rel="noopener noreferrer"&gt;Diagnostic Mental Models for Eval Comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Micro view: Sample-level diagnostics
&lt;/h3&gt;

&lt;p&gt;While high-level metric summaries alert you to outcomes like cost bloat or skill regression, opening an individual sample surfaces the &lt;strong&gt;Sample Details&lt;/strong&gt; panel for granular trace analysis:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fivqmzntm7aij2fjsvjht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fivqmzntm7aij2fjsvjht.png" alt="A panel showing a list of evaluation samples, each with an ID, epoch number, and the full input prompt text used for the evaluation." width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Transcript tab: How to read the LLM's conversation as it happened
&lt;/h4&gt;

&lt;p&gt;This tab shows the exact multi-turn conversation between the solver agent, the sandbox shell, and external tools. Use it to diagnose model reasoning versus environment noise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;System Prompt Verification&lt;/strong&gt;: Confirm that web search rules (&lt;code&gt;-T web_access=false&lt;/code&gt;) and automated time limits (&lt;code&gt;300 seconds&lt;/code&gt;) were correctly injected into the container environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu67zc1uf65f256l54qz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu67zc1uf65f256l54qz1.png" alt="A screenshot of a system instruction block labeled " width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skill Ingestion Check&lt;/strong&gt;: Verify whether the agent activated the skill. If activation didn’t occur, your eval is &lt;em&gt;mostly&lt;/em&gt; testing baseline model knowledge rather than skill utility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of one case where the skill was activated.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fatx7xwn28rskhb5bj1t8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fatx7xwn28rskhb5bj1t8.png" alt="A screenshot of a terminal-like interface showing a model call turn and a specific action: 'activate_skill' name: 'gemini-api' being executed." width="799" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This means:&lt;/strong&gt; The agent actually ingested &lt;code&gt;gemini-api&lt;/code&gt; instead of relying on baseline pre-training memory.&lt;/p&gt;

&lt;p&gt;Of note though, some of the tasks where skills were made available did NOT activate the skill. If you’re a skill author, you may want to rewrite the “activation criteria” (aka what situation calls for using the skill) such that it's more applicable to the specifics of relevant tasks.&lt;/p&gt;

&lt;h5&gt;
  
  
  Errors and reasoning loops
&lt;/h5&gt;

&lt;p&gt;Auditing transcript details allows you to distinguish model reasoning loops (e.g., repeated redundant tool calls) from sandbox environment noise (e.g., container timeouts or missing binary dependencies). For step-by-step diagnostic trace auditing procedures, see the README reference on &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#sandbox-noise-vs-model-reasoning" rel="noopener noreferrer"&gt;Sandbox Noise vs. Model Reasoning&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Scoring tab: Multi-fact verification breakdown
&lt;/h4&gt;

&lt;p&gt;Clicking the &lt;strong&gt;Scoring Tab&lt;/strong&gt; displays the empirical breakdown of our custom &lt;code&gt;multi_scorer&lt;/code&gt; on a sample:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqz48wt27uoj2ncldgzll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqz48wt27uoj2ncldgzll.png" alt="A screenshot of a scoring interface showing the input prompt, a multi-scorer score of 0.65, and a breakdown of six individual fact scores, five of which are marked 'C' (Correct) and one 'I' (Incorrect)." width="799" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;multi_scorer&lt;/code&gt; aggregates the results of checking the sample’s answer against individual binary yes/no Facts using  &lt;code&gt;model_graded_qa&lt;/code&gt;. The generated list of scores in the range [0.0, 1.0] is supplied to our &lt;code&gt;custom_reducer&lt;/code&gt; which calculates their arithmetic mean. From there, it applies quadratic score curving (

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;mean&lt;/span&gt;&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
) to ensure that the further from correct the mean of the supplied answers are, the lower the score is pulled. This allows the most correct answers to stand out immediately. In the &lt;strong&gt;Scoring Tab&lt;/strong&gt;, this downward curve maps a raw 5/6 fact score (
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;≈&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0.8333&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
) down to a normalized 0.65 sample score. For complete formulas and math derivations, see the README section on &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/agents/inspect-agent-skills-eval/README.md#3-decoupled-grader--multidimensional-rubrics" rel="noopener noreferrer"&gt;Atomic Fact Verification &amp;amp; Quadratic Curving&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Visual milestone: The transition to cohort analytics
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;inspect view&lt;/code&gt; provides outstanding deep-dive diagnostics for individual sample traces, evaluating dozens of models across multiple skill domains gets confusing fast (as you may have seen above). Weighing the correlations between skill inclusion vs exclusion and model changes against each other can be confounding and more art than science if we don’t find better ways to reason over them; it requires a structured matrix overview.&lt;/p&gt;

&lt;p&gt;Remember that mess of sectors of a 3d space I showed you earlier representing all the configs? Well, unless we find numeric ways to collapse that or quantify deeper comparison, you won’t get the granular information you need to choose between two similarly capable alternatives. Worse yet, you won’t be able to communicate this to the people with their hands at the purse strings (unless of course, that's you too).&lt;/p&gt;

&lt;p&gt;To make this easier, next time we’ll start down the road of understanding and displaying these things visually: As visualized in the cohort matrix linked above, understanding agent capability factor-by-factor requires cohort slicing across multi-dimensional metrics.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we scale our analysis from single log UI inspection to aggregate scoreboards with &lt;code&gt;inspect viz&lt;/code&gt;!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Codegolf: Build a container in Cloud Build</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Wed, 27 Mar 2024 01:19:28 +0000</pubDate>
      <link>https://dev.to/googlecloud/codegolf-build-a-container-in-cloud-build-4okn</link>
      <guid>https://dev.to/googlecloud/codegolf-build-a-container-in-cloud-build-4okn</guid>
      <description>&lt;p&gt;&lt;small&gt;&lt;em&gt;Banner Photo by &lt;a href="https://unsplash.com/@vki?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Viktor Kiryanov&lt;/a&gt; on Unsplash&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;I read Adam Ross's &lt;a href="https://dev.to/googlecloud/modernizing-cloudbuildyaml-for-container-builds-1je0"&gt;Modernizing cloudbuild.yaml for Container Builds&lt;/a&gt; and saw all his mentions of "character counts" and it got me thinking: &lt;/p&gt;

&lt;p&gt;How codegolf can we get with this? &lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Context: like regular golf, codegolf asks you to implement something in the least amount of character possible. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Starting with Adam's finishing iteration:&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;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Container&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Image'&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker:latest'&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . --tag "${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest"&lt;/span&gt;

&lt;span class="na"&gt;images&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;${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest"&lt;/span&gt;

&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;automapSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;substitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_IMAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service&lt;/span&gt;
  &lt;span class="na"&gt;_LOCATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us&lt;/span&gt;
  &lt;span class="na"&gt;_REPO&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initial character count: 358&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It appears his character count doesn't inline new lines, which is effectively the same as the &lt;code&gt;wc -c&lt;/code&gt; minus the &lt;code&gt;wc -l&lt;/code&gt;. I'll be using the following for my character counts: &lt;br&gt;
&lt;code&gt;expr $(wc -c cloudbuild.yaml | cut -d' ' -f1) - $(wc -l cloudbuild.yaml | cut -d ' ' -f1)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He notes that the introduction of &lt;a href="https://cloud.google.com/artifact-registry/docs/" rel="noopener noreferrer"&gt;Artifact Registry&lt;/a&gt; make this a bit longer than it would be in Container Registry, but we'll sit with that since it's the supported registry. &lt;/p&gt;

&lt;p&gt;We'll also keep with this last iteration's requirements: a project might have more than one registry, so we'll keep some parameterization. &lt;/p&gt;

&lt;p&gt;Each iteration I'll make sure is a valid build. For testing, I'll ensure &lt;code&gt;gcloud builds submit&lt;/code&gt; runs clean in a folder containing just this &lt;code&gt;cloudbuild.yaml&lt;/code&gt; file and my go-to &lt;a href="https://github.com/GoogleCloudPlatform/devrel-demos/blob/main/app-dev/python-frameworks-cloudrun/easteregg-shell/Dockerfile" rel="noopener noreferrer"&gt;codegolf Dockerfile&lt;/a&gt; (that's also a valid Cloud Run service, just for fun).&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in an ID?
&lt;/h2&gt;

&lt;p&gt;To start, Cloud Build steps don't require an ID, so we can remove that line entirely. Without an ID, the Cloud Build build details in the Google Cloud console will use the image name as the step details, so there is a slight usability disadvantage to this change, but nothing that affects functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-- id: 'Build Container Image'
-  name: 'gcr.io/cloud-builders/docker:latest'
&lt;/span&gt;&lt;span class="gi"&gt;+- name: 'gcr.io/cloud-builders/docker:latest'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Character count: 329.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deduplicate with dynamics
&lt;/h2&gt;

&lt;p&gt;We've also got the same image ID used multiple times. We want to keep the dynamic nature of this, but we also want to use substitutions in the value itself. We can use &lt;code&gt;dynamicSubstitutions&lt;/code&gt; to allow use of substitutions within other substitutions, but adding this adds ~27 characters to our file (and we're removing 45x2 characters so this is a win!)&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;steps&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcr.io/cloud-builders/docker:latest'&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . --tag "$_IMAGE_URI"&lt;/span&gt;

&lt;span class="na"&gt;images&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;$_IMAGE_URI"&lt;/span&gt;

&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;automapSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;dynamicSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;substitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_IMAGE_URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${_REPO}/${_IMAGE}:latest&lt;/span&gt;
  &lt;span class="na"&gt;_IMAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service&lt;/span&gt;
  &lt;span class="na"&gt;_LOCATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us&lt;/span&gt;
  &lt;span class="na"&gt;_REPO&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Character count: 326.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this point we can also remove the superfluous quotation marks, saving &lt;strong&gt;6&lt;/strong&gt; characters, bringing us down to &lt;strong&gt;320.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shortest values
&lt;/h2&gt;

&lt;p&gt;Now we get to the part where we work out: What's the minimum value for the variables we're using? &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;$PROJECT_ID&lt;/code&gt; is a &lt;a href="https://cloud.google.com/build/docs/configuring-builds/substitute-variable-values" rel="noopener noreferrer"&gt;default substitution&lt;/a&gt;, so we should keep that as is, and it will be dynamic for our project. &lt;/p&gt;

&lt;p&gt;We're already using the shortest Artifact Registry region, the &lt;a href="https://cloud.google.com/artifact-registry/docs/repositories/repo-locations#location-mr" rel="noopener noreferrer"&gt;US multi-region&lt;/a&gt; (the longest Google Cloud region could be up to 24 characters, so 2 is great!)&lt;/p&gt;

&lt;p&gt;But we can possibly do better with the Artifact Registry name and the Container Name. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;d&lt;/code&gt; (for "Docker") is a valid Artifact Registry repository name, and &lt;code&gt;i&lt;/code&gt; (for "image") are valid, so we can save ~15 characters there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;...
&lt;span class="gd"&gt;-  _IMAGE: service
&lt;/span&gt;&lt;span class="gi"&gt;+  _IMAGE: i
&lt;/span&gt;   _LOCATION: us
&lt;span class="gd"&gt;-  _REPO: container
&lt;/span&gt;&lt;span class="gi"&gt;+  _REPO: c
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Character count: 306.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimal variables
&lt;/h2&gt;

&lt;p&gt;Any of our underscore substitutions we can reduce down to two letters, the underscore and some useful character. (We're replacing "Image &lt;strong&gt;U&lt;/strong&gt;RL", "&lt;strong&gt;I&lt;/strong&gt;mage", "&lt;strong&gt;R&lt;/strong&gt;epo", and "&lt;strong&gt;L&lt;/strong&gt;ocation", respectively.)&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;steps&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;gcr.io/cloud-builders/docker:latest&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . --tag $_U&lt;/span&gt;

&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$_U&lt;/span&gt;

&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;automapSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;dynamicSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;substitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_U&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}:latest&lt;/span&gt;
  &lt;span class="na"&gt;_I&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;i&lt;/span&gt;
  &lt;span class="na"&gt;_L&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us&lt;/span&gt;
  &lt;span class="na"&gt;_R&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Character count: 254.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(We're now down below what Adam was before migrating to Artifact Registry! 😅)&lt;/p&gt;




&lt;h2&gt;
  
  
  Remove tags
&lt;/h2&gt;

&lt;p&gt;Another reduction we can make is removing the &lt;code&gt;:latest&lt;/code&gt; tags. "Latest" is used in Docker where there is &lt;a href="https://cloud.ibm.com/docs/Registry?topic=Registry-troubleshoot-docker-latest" rel="noopener noreferrer"&gt;a lack of tag&lt;/a&gt;, so this is implicit in our call (and it just so happens that the Cloud Builder we are referencing has a "latest" tag. (You can check this by going to &lt;a href="//gcr.io/cloud-builders/docker"&gt;gcr.io/cloud-builders/docker&lt;/a&gt; in the browser, which will show you information about this image in its registry. This image in particular will direct you to its Artifact Registry information, because &lt;a href="https://github.com/GoogleCloudPlatform/cloud-builders?tab=readme-ov-file#container-registry-deprecation" rel="noopener noreferrer"&gt;this registry has been automatically redirected&lt;/a&gt;.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&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;gcr.io/cloud-builders/docker&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . --tag $_U&lt;/span&gt;

&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$_U&lt;/span&gt;

&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;automapSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;dynamicSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;substitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_U&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}&lt;/span&gt;
  &lt;span class="na"&gt;_I&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;i&lt;/span&gt;
  &lt;span class="na"&gt;_L&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us&lt;/span&gt;
  &lt;span class="na"&gt;_R&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Character count: 240.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the args back
&lt;/h2&gt;

&lt;p&gt;The last step we can do to save space is to actually re-introduce the &lt;code&gt;args&lt;/code&gt; parameter, rather than using &lt;code&gt;script&lt;/code&gt;. In this case, we will be adding some characters for the separation of the arguments, but the biggest win is being able to remove the ~27 characters that the &lt;code&gt;automapSubstitutions&lt;/code&gt; config setting adds.&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;steps&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;gcr.io/cloud-builders/docker&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;build'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--tag'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$_U'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;$_U&lt;/span&gt;

&lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dynamicSubstitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;substitutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;_U&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${_L}-docker.pkg.dev/${PROJECT_ID}/${_R}/${_I}&lt;/span&gt;
  &lt;span class="na"&gt;_I&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;i&lt;/span&gt;
  &lt;span class="na"&gt;_L&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us&lt;/span&gt;
  &lt;span class="na"&gt;_R&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Character count: 213.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tip: Make sure you check what your default entrypoint is for images! In this case, I have also removed &lt;code&gt;docker&lt;/code&gt;, as my args are appended to the default entrypoint. The script needs the full command (I may have spent too long debugging this issue&lt;/em&gt; 😅&lt;em&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Twist
&lt;/h2&gt;

&lt;p&gt;Actually you only need &lt;strong&gt;22 characters&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;gcloud run deploy a
[ENTER]
[ENTER]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First time you run this you'll need 1 extra character (ENTER) to enable APIs, and also 1 extra character (ENTER) to create the Cloud Run Source Deploy image registry. (You can also use this method to automatically create the Artifact Registry for you, read more about this on my blog post: "&lt;a href="https://glasnt.com/blog/auto-provisioning-artifact-registry/" rel="noopener noreferrer"&gt;Auto-provisioning Artifact Registry though Cloud Run Source Deploys (glasnt.com)&lt;/a&gt;").&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;You can make a very short Cloud Build configuration to build images, but you should consider the readability of your scripts as you go. Addressing any cruft and modernizing your scripts can be an interesting exercise for you and your infrastructure. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;With thanks to Adam Ross for the review!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>codegolf</category>
      <category>cloudbuild</category>
    </item>
    <item>
      <title>Migrating from Secret Manager API to built-in secrets</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Sun, 19 Jun 2022 23:17:12 +0000</pubDate>
      <link>https://dev.to/googlecloud/migrating-from-secret-manager-api-to-built-in-secrets-4k06</link>
      <guid>https://dev.to/googlecloud/migrating-from-secret-manager-api-to-built-in-secrets-4k06</guid>
      <description>&lt;p&gt;Secret Manager is directly integrated into a number of products, including Cloud Run and Cloud Build. &lt;/p&gt;

&lt;p&gt;Many samples I've authored use the Secret Manager API by calling the API from within the code. This can be &lt;a href="https://cloud.google.com/secret-manager/docs/best-practices#coding_practices" rel="noopener noreferrer"&gt;more secure&lt;/a&gt;, but variable leakage is something that needs to be considered in any deployment. Removing the direct API calls can help with portability, and reduce the amount of dependencies in the deployment, and the complexity of the code itself. &lt;/p&gt;

&lt;p&gt;So let's look at how we can migrate to built-in secrets. &lt;/p&gt;




&lt;h2&gt;
  
  
  Integrating Secret Manager API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5i6owi4brzc6hfkm1ub8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5i6owi4brzc6hfkm1ub8.png" alt="Secret value: Input your secret value or import it directly from a file." width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Screenshot: "Secret value: Input your secret value or import it directly from a file.", showing a file upload dialog, or a text area input field.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you create secrets, you can create them from a file or from direct input. When you retrieve these secrets, you may presume they're a single value, or a file of content you have to process. &lt;/p&gt;

&lt;p&gt;Once you create the secret, you can &lt;a href="https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets#secretmanager-access-secret-version-python" rel="noopener noreferrer"&gt;access it with the Secret Manager API&lt;/a&gt; using a client library for Secret Manager available in many different languages. &lt;/p&gt;

&lt;p&gt;This post will use Python examples and packages, but the same patterns can be applied to other languages. &lt;/p&gt;




&lt;p&gt;For example, this code snippet will retrieve the latest version of the secret &lt;code&gt;mySecret&lt;/code&gt; in the current project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;google.auth&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.cloud&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secretmanager&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sm&lt;/span&gt;

&lt;span class="c1"&gt;# trick to detect current project
&lt;/span&gt;&lt;span class="n"&gt;_&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;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&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;sm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SecretManagerServiceClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mySecret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;projects/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/secrets/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/versions/latest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;secret_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;access_secret_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UTF-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;secret_value&lt;/code&gt; can then be used as required. This presumes that &lt;code&gt;mySecret&lt;/code&gt; is a single value, for example an API key.&lt;/p&gt;

&lt;p&gt;One way developers can include multiple secrets in one format is with &lt;code&gt;.env&lt;/code&gt; files. These files contain one or more key/value pairs which mean you can store multiple secrets in one file, then use helper packages to parse these values into your code. This prevents you from having to define multiple separate secret values. &lt;/p&gt;

&lt;p&gt;If &lt;code&gt;mySecret&lt;/code&gt; was a &lt;code&gt;.env&lt;/code&gt; file, I could load it using &lt;code&gt;python-dotenv:&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;## pip install python-dotenv
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or I could to similar with &lt;code&gt;django-environ&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;## pip install django_environ
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both of these examples, these packages presume that the values are in a &lt;code&gt;.env&lt;/code&gt; file within the same directory as the running process. Using &lt;code&gt;StringIO&lt;/code&gt; means that a string variable can be sent to these methods in a file-like object, so the values can be read as though they were a file.&lt;/p&gt;

&lt;p&gt;When using the Secret Manager API, you can run the application in any place where Google Cloud authentication works. That is, any Google Cloud service, or your local machine if you have set up &lt;code&gt;gcloud&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming built-in secrets
&lt;/h2&gt;

&lt;p&gt;If you have your secret configured as an environment variable, then you only have to retrieve it as an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;secret_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MYSECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This method prevents KeyErrors if the key is not found.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For products with built-in secrets, they may be made available in additional ways. &lt;/p&gt;

&lt;p&gt;In Cloud Build you can connect secrets &lt;a href="https://cloud.google.com/build/docs/securing-builds/use-secrets" rel="noopener noreferrer"&gt;directly to environment variables&lt;/a&gt;. But Cloud Run additionally allows &lt;a href="https://cloud.google.com/run/docs/configuring/secrets" rel="noopener noreferrer"&gt;mounting the secret as a volume&lt;/a&gt;, which means it can be read as a file. Any time the file is read, if you specify &lt;code&gt;latest&lt;/code&gt;, the current &lt;code&gt;latest&lt;/code&gt; will be received! However, you must mount the file in a new volume so you can't mount it directly in the default &lt;code&gt;.env&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If you want to adapt your code to handle a local &lt;code&gt;.env&lt;/code&gt; file, or a separately mounted file, or an environment variable, you will have to ensure all methods are possible in your code. &lt;/p&gt;

&lt;p&gt;You also need to consider which configuration takes priority, as by default both &lt;code&gt;python-dotenv&lt;/code&gt; and &lt;code&gt;django-environ&lt;/code&gt; accept the first declared value as the value they use. You can override this by using the &lt;code&gt;&lt;a href="https://github.com/theskumar/python-dotenv#variable-expansion" rel="noopener noreferrer"&gt;--override&lt;/a&gt;&lt;/code&gt; or &lt;code&gt;&lt;a href="https://github.com/theskumar/python-dotenv#variable-expansion" rel="noopener noreferrer"&gt;--overwrite&lt;/a&gt;&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;When developing applications, I might choose to say my local &lt;code&gt;.env&lt;/code&gt; file takes priority, then any mounted secrets, and then any declared variables. &lt;/p&gt;

&lt;p&gt;Another point to mention is that using &lt;code&gt;python-dotenv&lt;/code&gt;, if a file is not found or a value is empty, it silently continues. This means you can include the method calls without having to explicitly handle errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/secrets/.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MYSECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same code works for &lt;code&gt;django-environ&lt;/code&gt;, where you can just import in the order of priority without having to worry about missing files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/secrets/.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MYSECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in these examples, I'm choosing &lt;code&gt;/secrets/&lt;/code&gt;as my volume, and keeping the path the same name as the original file. You can choose any volume and path, as long as the volume is not already used by the application (for example, if you choose &lt;code&gt;/app/&lt;/code&gt; as your working directory, you cannot mount secrets there.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying built-in secrets
&lt;/h2&gt;

&lt;p&gt;To run this code locally, you'd create a &lt;code&gt;.env&lt;/code&gt; file with the contents &lt;code&gt;MYSECRET=serkitValue&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If you're committing this code to git, ensure you're not committing the secret file! Make sure you add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file!&lt;/p&gt;

&lt;p&gt;You can also choose to ignore any contents of your &lt;code&gt;.gitignore&lt;/code&gt; file in your Google Cloud commands by adding &lt;code&gt;.gitignore&lt;/code&gt;'s contents to &lt;code&gt;&lt;a href="https://cloud.google.com/sdk/gcloud/reference/topic/gcloudignore" rel="noopener noreferrer"&gt;.gcloudignore&lt;/a&gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.git
#!include:.gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then create the secret from this file with &lt;code&gt;gcloud&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud secrets create mySecret &lt;span class="nt"&gt;--data-file&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Cloud Build, you will need to ensure the secret is available in the environment (&lt;a href="https://cloud.google.com/build/docs/securing-builds/use-secrets#example_accessing_secrets_from_scripts_and_processes" rel="noopener noreferrer"&gt;which your script can then use&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&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;python:slim&lt;/span&gt;
   &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip&lt;/span&gt;
   &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;install'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-r'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requirements.txt'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--user'&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;python:slim&lt;/span&gt;
   &lt;span class="na"&gt;secretEnv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MYSECRET'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
   &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
   &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main.py'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;availableSecrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;secretManager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;versionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;projects/$PROJECT_ID/secrets/mySecret/versions/latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MYSECRET'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also reference secrets in the args call itself, &lt;a href="https://cloud.google.com/build/docs/securing-builds/use-secrets#access-utf8-secrets" rel="noopener noreferrer"&gt;using bash variables&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For Cloud Run, you'll have to deploy the service specifying either a mount or an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# for mounted volume&lt;/span&gt;
gcloud run deploy myservice &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;  &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--update-secrets&lt;/span&gt; /secrets/.env&lt;span class="o"&gt;=&lt;/span&gt;mySecret:latest

&lt;span class="c"&gt;# for environment variable&lt;/span&gt;
gcloud run deploy myservice &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;  &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--update-secrets&lt;/span&gt; &lt;span class="nv"&gt;MYSECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mySecret:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  And never forget IAM!
&lt;/h2&gt;

&lt;p&gt;Don't forget: you'll also need to make sure the service account you're using has &lt;a href="https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets#access" rel="noopener noreferrer"&gt;permissions&lt;/a&gt; to access your secret!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Katie is a Developer Advocate for Google Cloud, focusing on Python and Serverless. She tweets &lt;a href="https://twitter.com/glasnt" rel="noopener noreferrer"&gt;@glasnt&lt;/a&gt; about clouds, crafts, and cats.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>secretmanager</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using Workflows to dynamically update a Datasette website</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Sun, 20 Feb 2022 22:04:32 +0000</pubDate>
      <link>https://dev.to/googlecloud/using-workflows-to-dynamically-update-a-datasette-website-1bjk</link>
      <guid>https://dev.to/googlecloud/using-workflows-to-dynamically-update-a-datasette-website-1bjk</guid>
      <description>&lt;p&gt;A while ago I spoke with &lt;a href="https://twitter.com/simonw" rel="noopener noreferrer"&gt;Simon Willison&lt;/a&gt; about the sorts of things he'd like to have in a cloud provider in order to deploy &lt;a href="https://datasette.io/" rel="noopener noreferrer"&gt;Datasette&lt;/a&gt; nicely and repeatedly. A few of the things mentioned were being able to attach to object storage, especially when databases were particularly large and it didn't make sense to mount them into the container image itself. &lt;/p&gt;

&lt;p&gt;With the preview of the &lt;a href="https://cloud.google.com/run/docs/about-execution-environments" rel="noopener noreferrer"&gt;second generation execution environment&lt;/a&gt; with Cloud Run, one of the many new features is the ability to &lt;a href="https://cloud.google.com/run/docs/tutorials/network-filesystems-fuse" rel="noopener noreferrer"&gt;mount Cloud Storage as a network filesystem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this, you can attach any Cloud Storage bucket and have a Datasette web service serve data from that source. &lt;/p&gt;

&lt;p&gt;This works very well when you have one database that you want to serve. However, one of the &lt;a href="https://github.com/simonw/datasette/issues/43#issuecomment-344180866" rel="noopener noreferrer"&gt;known limitations of Datasette&lt;/a&gt;, where if you serve a folder of content, the glob of files in that folder is loaded at server start time, and not refreshed unless the server is restarted.&lt;/p&gt;

&lt;p&gt;There are few ways we can work around this issue. You could have the process within the container restart when the mounted filesystem changes, using something like &lt;code&gt;inotify&lt;/code&gt; (&lt;code&gt;fsevent&lt;/code&gt; on macOS). You could also consider periodically starting, but that may mean restarting when not required, or not often enough. For a sufficiently low traffic system, you'd end up automatically booting from cold after a time of inactivity, anyway. &lt;/p&gt;

&lt;p&gt;The way I chose to solve this was to use the event of an object being uploaded to the bucket to trigger an update of the service, thus restarting Datasette (albeit dramatically). &lt;/p&gt;

&lt;p&gt;And since I was now listening to the upload event, I thought, why not do more processing while I'm here?&lt;/p&gt;

&lt;p&gt;I'd recently learnt from &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-picadaily-lab6#0" rel="noopener noreferrer"&gt;Lab 6&lt;/a&gt; of the &lt;a href="https://g.co/codelabs/serverless-workshop" rel="noopener noreferrer"&gt;Pic-A-Daily Workshop&lt;/a&gt; how to use &lt;a href="https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop/tree/master/workflows" rel="noopener noreferrer"&gt;Workflows to handle events&lt;/a&gt;, so I thought I'd try developing one of my own. &lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://github.com/glasnt/dynamic-datasette" rel="noopener noreferrer"&gt;github.com/glasnt/dynamic-datasette&lt;/a&gt;, and once deployed it works like this: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you upload an new file to the upload bucket&lt;/li&gt;
&lt;li&gt;the event of that file being uploaded triggers a function, that starts a Workflow&lt;/li&gt;
&lt;li&gt;the workflow then follows through a number of steps:

&lt;ul&gt;
&lt;li&gt;sends the file for processing:

&lt;ul&gt;
&lt;li&gt;if the file is already an SQLite database, then it sends it off to the serving bucket&lt;/li&gt;
&lt;li&gt;if it's one of the types of files it knows about, it uses one of the many &lt;code&gt;*-to-sqlite&lt;/code&gt; tools Simon has made. For example, &lt;code&gt;csvs-to-sqlite&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Once converted, the resulting SQLite database file is uploaded to the serving bucket&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;updates &lt;a href="https://docs.datasette.io/en/stable/metadata.html" rel="noopener noreferrer"&gt;metadata&lt;/a&gt; for Datasette:

&lt;ul&gt;
&lt;li&gt;this step mostly just ensures that the Datasette service displays the datetime when this workflow was run, so users can know how new their data is that's being served&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;restarts the web service

&lt;ul&gt;
&lt;li&gt;This step uses the &lt;code&gt;replaceService&lt;/code&gt; API to force a 'restart', deploying a new revision of the Datasette service, ensuring that the latest information in the serving bucket is being relayed to users. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The Datasette container mounts the serving bucket, and metadata.json in that same bucket, then just calls "&lt;code&gt;datasette serve&lt;/code&gt;" on the mounted folder.&lt;/p&gt;

&lt;p&gt;You can try this out on your own project by deploying the code with Terraform, following the instructions in the README.md. &lt;/p&gt;

&lt;p&gt;Try extending the functionality of the &lt;code&gt;process-upload&lt;/code&gt; function to do more steps, or adding more metadata to the hosted service.&lt;/p&gt;




&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@victor_g?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Victor&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/pipes?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Open Source Credit Survey</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Tue, 01 Feb 2022 21:31:08 +0000</pubDate>
      <link>https://dev.to/glasnt/open-source-credit-survey-3jm0</link>
      <guid>https://dev.to/glasnt/open-source-credit-survey-3jm0</guid>
      <description>&lt;p&gt;&lt;em&gt;The University of Vermont is conducting a survey to help understand how people receive credit for tasks in open source, and are seeking your participation!&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Survey: &lt;a href="https://qualtrics.uvm.edu/jfe/form/SV_1zUs19oVcZJ0SPA" rel="noopener noreferrer"&gt;https://qualtrics.uvm.edu/jfe/form/SV_1zUs19oVcZJ0SPA&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Today's open source world has similarities to the world of open source when I &lt;a href="https://opensource.com/life/15/10/octohat-github-non-code-contribution-tracker" rel="noopener noreferrer"&gt;first wrote about this problem in 2015&lt;/a&gt;. I wrote a tool that helps discover who is contributing to GitHub projects based on the contributions that aren't code. In 2022, we are still contributing countless hours of time towards communities to drive and improve open source across many industries, but we don't know how much of that work is credited beyond automated tools like this. &lt;/p&gt;

&lt;p&gt;I'm a member of the &lt;a href="https://vermontcomplexsystems.org/partner/OCEAN/" rel="noopener noreferrer"&gt;OCEAN&lt;/a&gt;, a research team from the University of Vermont Complex Systems Center, and we're collaborating with industry experts from Google, OSI, IEEE, PSF, and OSSF, among others. You may have recently heard about our project on &lt;a href="https://podcast.chaoss.community/50" rel="noopener noreferrer"&gt;CHAOSSCast&lt;/a&gt;, or being shout-out on &lt;a href="https://podcast.sustainoss.org/98" rel="noopener noreferrer"&gt;SustainOSS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As we've written in our correspondence in &lt;a href="https://www.nature.com/articles/s43588-020-00011-w" rel="noopener noreferrer"&gt;Nature Computational Science&lt;/a&gt;, we believe that having a recognition model for the work done in any open source community can help more in just gaining credit for code, but providing clear signaling of the work done for the community. &lt;/p&gt;

&lt;p&gt;As part of this work, the University of Vermont team is conducting a survey about how people receive credit for the tasks they do as part of open source projects.&lt;/p&gt;

&lt;p&gt;The survey asks about your experiences with receiving credit on open source projects. The survey asks multiple choice questions about how often you received credit for the tasks you did, and written response questions about what did and did not go well on open source projects.&lt;/p&gt;

&lt;p&gt;We hope that with this information, we can improve the  equity of contributions across all of open source.&lt;/p&gt;




&lt;p&gt;Click on this link to participate in the survey: &lt;a href="https://qualtrics.uvm.edu/jfe/form/SV_1zUs19oVcZJ0SPA" rel="noopener noreferrer"&gt;https://qualtrics.uvm.edu/jfe/form/SV_1zUs19oVcZJ0SPA&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://vermontcomplexsystems.org/partner/OCEAN/" rel="noopener noreferrer"&gt;OCEAN&lt;/a&gt; is a partnership between the Google Open Source Program Office and the Vermont Complex Systems Center. Google does not have any influence on the survey, and will be unable to see any individual responses.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>When you're not around: trigger Cloud Run on a schedule</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Fri, 22 Jan 2021 00:00:31 +0000</pubDate>
      <link>https://dev.to/googlecloud/when-you-re-not-around-trigger-cloud-run-on-a-schedule-53p4</link>
      <guid>https://dev.to/googlecloud/when-you-re-not-around-trigger-cloud-run-on-a-schedule-53p4</guid>
      <description>&lt;p&gt;On this edition of Serverless Expeditions, we take a look at scheduling automated Cloud Run jobs with Cloud Scheduler.&lt;/p&gt;

&lt;p&gt;Check out the video version of this blog post.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/XIwbIimM49Y"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Source code for this blog post is available on GitHub under the "scheduled-cloud-run" folder.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/GoogleCloudPlatform" rel="noopener noreferrer"&gt;
        GoogleCloudPlatform
      &lt;/a&gt; / &lt;a href="https://github.com/GoogleCloudPlatform/serverless-expeditions" rel="noopener noreferrer"&gt;
        serverless-expeditions
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;&lt;a href="http://cloud.run/" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; is designed to host a containerised web service -- any language you like, as long as the service &lt;a href="https://cloud.google.com/run/docs/reference/container-contract#port" rel="noopener noreferrer"&gt;listens on port 8080&lt;/a&gt; -- but your code can run on events other than a user visiting your site. &lt;/p&gt;

&lt;p&gt;An example of this design pattern would be a nightly billing job: you want to process billing data nightly at 1am, but you don't want to be the person to click a button in the middle of the night. &lt;/p&gt;

&lt;p&gt;If you wanted to implement such a receiving service in NodeJS, you could implement it like in our example code: at the top of the code, create and initialize an &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express app&lt;/a&gt; that listens for incoming HTTP calls. Note that Cloud Scheduler uses the “text/plain” content type, so you'll have to tell body-parser to parse more than the default “application/json” type. The POST handler parses the minimum balance that was sent to it from Cloud Scheduler, and then sends that off to the billing method later in the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/scheduler" rel="noopener noreferrer"&gt;Cloud Scheduler&lt;/a&gt; is a managed service offered on Google Cloud, where you can create jobs on a cron schedule that either makes a HTTPS call, or publishes a Pub/Sub message.  In this example, you can setup a scheduled job that runs at 1am every night by entering the &lt;a href="https://crontab.guru/#0_1_*_*_*" rel="noopener noreferrer"&gt;cron value&lt;/a&gt; &lt;code&gt;0 1 * * *&lt;/code&gt; in the Frequency field, and setting the HTTP Target to be the URL of your Cloud Run service you created earlier.&lt;/p&gt;

&lt;p&gt;However, you need to make sure that this billing service is private, and not accessible by anyone on the internet. To convert a &lt;a href="https://cloud.google.com/run/docs/authenticating/public" rel="noopener noreferrer"&gt;public service&lt;/a&gt; to private, you need to remove the &lt;code&gt;allUsers&lt;/code&gt; member from the service -- that is, deny anyone from accessing it. To then allow Cloud Scheduler to access the service, create a new service account, assigning it the Cloud Run Invoker role. Then set that service account's email identifier in the &lt;a href="https://cloud.google.com/scheduler/docs/http-target-auth" rel="noopener noreferrer"&gt;Auth Header setting&lt;/a&gt; in Cloud Scheduler, under "Add OIDC token". OIDC, or &lt;a href="https://openid.net/connect/" rel="noopener noreferrer"&gt;OpenID Connect&lt;/a&gt;, is a small layer on top of OAuth that handles identity. This field tells Cloud Scheduler to use the new service account's identity for running the job. &lt;/p&gt;

&lt;p&gt;The free tier of Cloud Scheduler allows you &lt;a href="https://cloud.google.com/scheduler/pricing" rel="noopener noreferrer"&gt;3 jobs for free&lt;/a&gt;, no matter how many times the job is run. Each job after that is USD$0.10 each. You may also incur costs for excess &lt;a href="https://cloud.google.com/run/pricing" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; execution time, database costs, etc.&lt;/p&gt;

&lt;p&gt;By using Google Cloud managed services that connect to Cloud Run, you can build more complex architectures, and automate manual processes to make your developer live easier. &lt;/p&gt;




&lt;h2&gt;
  
  
  About Serverless Expeditions
&lt;/h2&gt;

&lt;p&gt;Serverless Expeditions is a fun and cheeky video series that looks at what serverless means and how to build serverless apps with Google Cloud. &lt;/p&gt;

&lt;p&gt;Follow these hosts on Twitter at &lt;a href="https://twitter.com/glasnt" rel="noopener noreferrer"&gt;@glasnt&lt;/a&gt; and &lt;a href="https://twitter.com/martinomander" rel="noopener noreferrer"&gt;@martinomander&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverlessexpeditions</category>
      <category>googlecloudrun</category>
      <category>cloudrun</category>
    </item>
    <item>
      <title>Running multiple versions of Python in Cloud Run</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Fri, 11 Dec 2020 01:05:06 +0000</pubDate>
      <link>https://dev.to/googlecloud/running-multiple-versions-of-python-in-cloud-run-29me</link>
      <guid>https://dev.to/googlecloud/running-multiple-versions-of-python-in-cloud-run-29me</guid>
      <description>&lt;p&gt;On this episode of Serverless Expeditions, we take a look at how to deploy multiple versions of Python in Cloud Run.&lt;/p&gt;

&lt;p&gt;Check out the video version of this blog post.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-fbcOnrN-Eo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;Running multiple versions of Python in Cloud Run - Serverless Expeditions (YouTube.com)&lt;p&gt;&lt;/p&gt;




&lt;p&gt;As new versions of Python are released, trying to choose between the new and shiny or the old and stable can be an issue. But if you are deploying on &lt;a href="http://cloud.run/" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt;, you don't have to choose between the two, you can run both. &lt;/p&gt;

&lt;p&gt;Imagine this hypothetical scenario: you are an SRE for a company with a custom storefront, maintaining a fleet of virtual machines running various components, all with their own versions of Python. To improve reliability, you can upgrade all the components to use one version of Python, and move away from virtual machines, and into something more scalable, like serverless. &lt;/p&gt;

&lt;p&gt;This upgrade won't happen overnight, and the storefront must keep running in the meantime. So your plan is multi-step: migrate from virtual machines to serverless first, then make the language upgrades. But before migrating the existing systems, a new service coming online could be deployed serverless first, skipping the virtual machine all together. &lt;/p&gt;

&lt;p&gt;When choosing which serverless platform to upgrade to, limitations may exist on the languages available. For example, Cloud Functions only supports &lt;a href="https://cloud.google.com/functions/docs/concepts/python-runtime" rel="noopener noreferrer"&gt;specific versions of Python&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;However, the newest serverless offering from Google Cloud -- Cloud Run -- doesn't have this limitation. Cloud Run defines services as container images, which are defined by Dockerfiles, and can specify any base image. There is an entire repository of &lt;a href="https://hub.docker.com/_/python" rel="noopener noreferrer"&gt;official Python images on Docker Hub&lt;/a&gt; which you can source from, by specifying the base image in the FROM line of your Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:2.7
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.9
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you migrate each service from its original virtual machine, you can set the exact version of Python it was originally running. You can also upgrade each service in isolation, as your team has time and resources. &lt;/p&gt;

&lt;p&gt;Once your dependencies have been upgraded and you want to test your service, you can deploy your new revision to only a limited number of users, using the &lt;a href="https://cloud.google.com/run/docs/rollouts-rollbacks-traffic-migration#gradual" rel="noopener noreferrer"&gt;Traffic Splitting feature of Cloud Run.&lt;/a&gt; You can set your new revision to serve, say, only 5% of requests, so if there is something wrong, only some of your users are affected, and you can quickly rollback if there are any issues. &lt;/p&gt;

&lt;p&gt;By migrating each service in turn, you are able to control each service in isolation, allowing for a more incremental upgrade process, and a more reliable environment. &lt;/p&gt;

&lt;p&gt;By being able to specify the exact version of Python being used in your project, you can run multiple services each with their own isolated Python environment, which allows you more time to upgrade each service to supported versions of Python. &lt;/p&gt;

&lt;p&gt;It also allows you to try the newest versions of Python -- &lt;a href="https://hub.docker.com/_/python?tab=tags&amp;amp;page=1&amp;amp;name=3.10&amp;amp;ordering=last_updated" rel="noopener noreferrer"&gt;such as prereleases of Python 3.10&lt;/a&gt; -- as soon as their images are available on Docker Hub.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Serverless Expeditions
&lt;/h2&gt;

&lt;p&gt;Serverless Expeditions is a fun and cheeky video series that looks at what serverless means and how to build serverless apps with Google Cloud. &lt;/p&gt;

&lt;p&gt;Follow these hosts on Twitter at &lt;a href="https://twitter.com/glasnt" rel="noopener noreferrer"&gt;@glasnt&lt;/a&gt; and &lt;a href="https://twitter.com/martinomander" rel="noopener noreferrer"&gt;@martinomander&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverlessexpeditions</category>
      <category>googlecloudrun</category>
      <category>python</category>
    </item>
    <item>
      <title>"Point-and-click" continuous deployment with Cloud Run</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Thu, 10 Dec 2020 20:36:47 +0000</pubDate>
      <link>https://dev.to/googlecloud/point-and-click-continuous-deployment-with-cloud-run-4e4</link>
      <guid>https://dev.to/googlecloud/point-and-click-continuous-deployment-with-cloud-run-4e4</guid>
      <description>&lt;p&gt;On this edition of Serverless Expeditions, we take a look at how to deploy a GitHub repo on GitHub, though Cloud Run's new "point-and-click" integration, no Dockerfile required. &lt;/p&gt;

&lt;p&gt;Check out the video version of this blog post.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Nf3KAY-i6zw"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;Say you've created a website for a travel blog, and you want to host it online. You've got your code on GitHub, and you have a README of instructions of how a friend could deploy a copy of your website: they have to have your programming language installed on their machine, install some package dependencies, then run a command to start a web server. &lt;/p&gt;

&lt;p&gt;If you want to host your website, you need a way that you can run these steps any time you want to update your code. This process is known as Continuous Deployment: when you commit a change to code a process is started that takes the new version of your code, performs the prerequisite tasks, and then starts a web server of your new code. &lt;/p&gt;

&lt;p&gt;Cloud Run, a serverless platform from Google Cloud, allows you to to bring any container to serve your website, no matter the programming language used inside, as long as it follows the &lt;a href="https://cloud.google.com/run/docs/reference/container-contract" rel="noopener noreferrer"&gt;Runtime Container contract&lt;/a&gt;: a web server must be listening for requests on &lt;code&gt;0.0.0.0&lt;/code&gt;, on port 8080 (or the configured &lt;code&gt;PORT&lt;/code&gt; environment variable). &lt;/p&gt;

&lt;p&gt;So, if you have a website, how do you turn it into a container? You could write a &lt;a href="https://docs.docker.com/engine/reference/builder/" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt;, a set of instructions to build a container image based on your setup. But if you're using a common language like Python, Go, or Ruby, the instructions are going to be similar for many websites: copy your code into the container, install your dependencies, and start the web server. &lt;/p&gt;

&lt;p&gt;You can setup your Cloud Run service through the Google Cloud Console using the new &lt;a href="https://cloud.google.com/run/docs/continuous-deployment-with-cloud-build" rel="noopener noreferrer"&gt;walkthrough integration with Cloud Build&lt;/a&gt;: you connect your GitHub repo with Cloud Run, and set up your website to be built any time your code updates. &lt;/p&gt;

&lt;p&gt;However, instead of having to write a Dockerfile, you can now use &lt;a href="https://github.com/GoogleCloudPlatform/buildpacks" rel="noopener noreferrer"&gt;Buildpacks&lt;/a&gt;, a set of common instructions for popular programming languages that help you build your code into a container image that Cloud Run can deploy. &lt;/p&gt;

&lt;p&gt;If you're using a language like Go, Node.js, Python, Java or .NET Core, this will automatically be detected when building a container using Buildpacks. These common languages and their respective package installation processes are handled for you; all you need to do is provide the command to start your web server in a special file called Procfile. &lt;/p&gt;

&lt;p&gt;In the example in the video, Martin is using Flask, a Python-based web framework. To start his application, he runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 -m flask run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So his Procfile will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: python3 -m flask run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "web" in this case means "web server", and is the special term Buildpack looks for in a Procfile to start the web server. &lt;/p&gt;

&lt;p&gt;He should also configure his Flask app to listen on the expected IP and port. He can either add that to his app.run command, or in the Procfile itself as part of starting Flask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: python3 -m flask run --host 0.0.0.0 --port $PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As long as he adds this file to his codebase, he doesn't need to change anything else about his website to host it on Cloud Run. &lt;/p&gt;

&lt;p&gt;Once configured, any commits made to the GitHub repo -- from the command line, VSCode, GitHub's website, or anywhere else -- are automatically detected and deployed.&lt;/p&gt;




&lt;p&gt;To learn more about the concepts discussed in this video, read more: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://cloud.google.com/run/docs/reference/container-contract" rel="noopener noreferrer"&gt;Container runtime contract | Cloud Run Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://cloud.google.com/run/docs/continuous-deployment-with-cloud-build" rel="noopener noreferrer"&gt;Continuous deployment from Git using Cloud Build&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://github.com/GoogleCloudPlatform/buildpacks" rel="noopener noreferrer"&gt;GoogleCloudPlatform/buildpacks | GitHub.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can try some of the sample buildpack apps at &lt;a href="https://github.com/GoogleCloudPlatform/buildpack-samples" rel="noopener noreferrer"&gt;https://github.com/GoogleCloudPlatform/buildpack-samples&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  About Serverless Expeditions
&lt;/h2&gt;

&lt;p&gt;Serverless Expeditions is a fun and cheeky video series that looks at what serverless means and how to build serverless apps with Google Cloud. &lt;/p&gt;

&lt;p&gt;Follow these hosts on dev.to at &lt;a href="https://dev.to/glasnt"&gt;@glasnt&lt;/a&gt; and &lt;a href="https://dev.to/momander"&gt;@martinomander&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverlessexpeditions</category>
      <category>cloudrun</category>
      <category>continuousdeployment</category>
      <category>buildpacks</category>
    </item>
    <item>
      <title>Deploying Django and Wagtail on Google Cloud</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Thu, 01 Oct 2020 23:03:15 +0000</pubDate>
      <link>https://dev.to/googlecloud/deploying-django-and-wagtail-on-google-cloud-17h0</link>
      <guid>https://dev.to/googlecloud/deploying-django-and-wagtail-on-google-cloud-17h0</guid>
      <description>&lt;p&gt;On this edition of Serverless Expeditions, we take a look at how to deploy more complex applications on Google Cloud, using Django as an example. &lt;/p&gt;

&lt;p&gt;Check out the video version of this blog post.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Pk21gaNO6ag"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;When moving legacy applications into serverless infrastructure, there is going to be some sort of state involved: there will be a database, media, and other components. Traditionally these can all be run within the confines of a virtual machine, where all the services can talk to each other in that environment. But when moving to serverless, all these components need to be specifically provisioned and connected together. &lt;/p&gt;

&lt;p&gt;While Cloud Run itself is stateless, it can be connected to other stateful services such as &lt;a href="https://cloud.google.com/sql/" rel="noopener noreferrer"&gt;Cloud SQL&lt;/a&gt; for relational databases, or &lt;a href="https://cloud.google.com/storage" rel="noopener noreferrer"&gt;Cloud Storage&lt;/a&gt; for static media hosting. &lt;/p&gt;

&lt;p&gt;In today's episode, we'll discuss &lt;a href="https://www.youtube.com/watch?v=AoNulKfMl_Q&amp;amp;list=PLIivdWyY5sqKiWvnaA5A8F3UQ0Xu5i49U" rel="noopener noreferrer"&gt;containerizing&lt;/a&gt; and deploying a typical Django application using Cloud Run and other Google Cloud services. The steps described can be applied to any website whose CMS uses the &lt;a href="http://djangoproject.com/" rel="noopener noreferrer"&gt;Django web framework&lt;/a&gt;, such as &lt;a href="https://wagtail.io/" rel="noopener noreferrer"&gt;Wagtail&lt;/a&gt; or &lt;a href="https://www.django-cms.org/en/" rel="noopener noreferrer"&gt;Django CMS&lt;/a&gt;. &lt;/p&gt;




&lt;p&gt;Django supports both PostgreSQL and MySQL, both of which are available in Cloud SQL, a managed database service. When connecting to a Cloud Run service, specifying &lt;code&gt;&lt;a href="https://cloud.google.com/sql/docs/postgres/connect-run#command-line" rel="noopener noreferrer"&gt;--add-cloudsql-instances&lt;/a&gt;&lt;/code&gt; allows the service and the database to communicate over an automatically encrypted connection. The authentication from the application to the database is achieved by specifying a connection URL, stored securely in &lt;a href="https://cloud.google.com/secret-manager" rel="noopener noreferrer"&gt;Secret Manager&lt;/a&gt;, loaded using the &lt;a href="https://pypi.org/project/google-cloud-secret-manager/" rel="noopener noreferrer"&gt;Python API&lt;/a&gt;. The database can then be populated by running &lt;code&gt;&lt;a href="https://docs.djangoproject.com/en/3.0/ref/django-admin/#django-admin-migrate" rel="noopener noreferrer"&gt;./manage.py migrate&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Static and media assets can be migrated to use Cloud Storage, with help from the &lt;code&gt;&lt;a href="https://pypi.org/project/django-storages/" rel="noopener noreferrer"&gt;django-storages&lt;/a&gt;&lt;/code&gt; package. By creating a new storage bucket and specifying the name in the django settings.py, then running &lt;code&gt;&lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/staticfiles/#collectstatic" rel="noopener noreferrer"&gt;./manage.py collectstatic&lt;/a&gt;&lt;/code&gt;, the images and assets are uploaded to the bucket. &lt;/p&gt;

&lt;p&gt;However, those two manage.py commands -- &lt;code&gt;migrate&lt;/code&gt; and &lt;code&gt;collectstatic&lt;/code&gt; -- are typically run within an interactive terminal. Cloud Run has no such terminal, so these commands need to be run elsewhere. One method is to run these commands as part of the continuous integration process with &lt;a href="https://cloud.google.com/cloud-build" rel="noopener noreferrer"&gt;Cloud Build&lt;/a&gt;. Adding a 'migrate' step using the &lt;a href="https://github.com/GoogleCloudPlatform/ruby-docker/tree/master/app-engine-exec-wrapper" rel="noopener noreferrer"&gt;app-engine-exec-wrapper&lt;/a&gt; between the typical build and deploy steps allows migration to happen as part of deployment. &lt;/p&gt;

&lt;p&gt;If database migrations need to be manual, Cloud SQL proxy can be used to interact with a &lt;a href="https://cloud.google.com/sql/docs/postgres/connect-admin-proxy" rel="noopener noreferrer"&gt;Cloud SQL database locally&lt;/a&gt;. Details of how to set this up with Docker Compose are &lt;a href="https://github.com/GoogleCloudPlatform/django-demo-app-unicodex/blob/latest/docs/70-manual-deployments.md" rel="noopener noreferrer"&gt;available here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With a managed database, highly-available static assets, automated deployment pipelines and robust containerisation solutions, you can migrate your Django applications to Google Cloud with confidence.&lt;/p&gt;




&lt;p&gt;If you want to get started with these concepts, check out the Django Codelabs: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-run-django/#0" rel="noopener noreferrer"&gt;Django on Cloud Run&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-run-djangocms/#0" rel="noopener noreferrer"&gt;Wagtail on Cloud Run&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-run-djangocms/#0" rel="noopener noreferrer"&gt;Django CMS on Cloud Run&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also find a sample implementation of these concepts at &lt;a href="https://github.com/GoogleCloudPlatform/django-demo-app-unicodex" rel="noopener noreferrer"&gt;GoogleCloudPlatform/django-demo-app-unicodex&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  About Serverless Expeditions
&lt;/h2&gt;

&lt;p&gt;Serverless Expeditions is a fun and cheeky video series that looks at what serverless means and how to build serverless apps with Google Cloud. &lt;/p&gt;

&lt;p&gt;Follow these hosts on Twitter at &lt;a href="https://twitter.com/glasnt" rel="noopener noreferrer"&gt;@glasnt&lt;/a&gt; and &lt;a href="https://twitter.com/martinomander" rel="noopener noreferrer"&gt;@martinomander&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serverlessexpeditions</category>
      <category>django</category>
      <category>cloudrun</category>
    </item>
    <item>
      <title>Automating Python package releases by extending git</title>
      <dc:creator>Katie McLaughlin</dc:creator>
      <pubDate>Mon, 10 Aug 2020 03:12:30 +0000</pubDate>
      <link>https://dev.to/glasnt/automating-python-package-releases-by-extending-git-2ncd</link>
      <guid>https://dev.to/glasnt/automating-python-package-releases-by-extending-git-2ncd</guid>
      <description>&lt;p&gt;Like many automation workflows, you end up connecting different operations together that chain together. &lt;/p&gt;

&lt;p&gt;In this post I'll describe how after initial setup, you can cut and release a new patch version of your GitHub hosted Python package by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git version-bump patch
git release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Link One
&lt;/h3&gt;

&lt;p&gt;In Python's packaging ecosystem, versions are important. &lt;code&gt;version&lt;/code&gt; must be declared in a package's &lt;code&gt;setup.py&lt;/code&gt;. It doesn't have to be defined directly as a parameter of &lt;code&gt;setup()&lt;/code&gt;, but it must be declared. You could setup a &lt;code&gt;yourpackage/__version__.py&lt;/code&gt;, which allows you to at have the version stored outside of &lt;code&gt;setup.py&lt;/code&gt;, but you still need to read from that file. &lt;/p&gt;

&lt;p&gt;But why define the version in a file when you can dynamically query it from a git tag? How can you get &lt;code&gt;setup.py&lt;/code&gt; to understand the version? &lt;/p&gt;

&lt;p&gt;If you &lt;code&gt;pip install versioneer&lt;/code&gt; and &lt;code&gt;versioneer install&lt;/code&gt;  it into your package, you can get &lt;a href="https://github.com/warner/python-versioneer/" rel="noopener noreferrer"&gt;versioneer&lt;/a&gt; to query git directly to get the current tag. This will work within the context of git. This is important later. Versioneer itself isn't a package dependency of your package, it's self-contained in a &lt;code&gt;_versioneer.py&lt;/code&gt; file&lt;/p&gt;




&lt;h3&gt;
  
  
  Link Two
&lt;/h3&gt;

&lt;p&gt;Git can &lt;a href="https://github.com/git/git/blob/d70a9eb611a9d242c1d26847d223b8677609305b/git.c#L779" rel="noopener noreferrer"&gt;execute external commands&lt;/a&gt; as if they were git commands. If you have an executable file in your &lt;code&gt;PATH&lt;/code&gt; that starts with &lt;code&gt;git-&lt;/code&gt;, e.g. &lt;code&gt;git-thing&lt;/code&gt;, it also works as &lt;code&gt;git thing&lt;/code&gt; (&lt;a href="https://github.com/git/git/blob/d70a9eb611a9d242c1d26847d223b8677609305b/git.c#L691" rel="noopener noreferrer"&gt;with a space rather than a hyphen&lt;/a&gt;).  As of &lt;a href="https://github.com/git/git/blob/master/Documentation/RelNotes/2.20.0.txt#L98" rel="noopener noreferrer"&gt;git v2.20.0&lt;/a&gt;, &lt;code&gt;git help --all&lt;/code&gt; now lists these external commands. The implementation language of the executable doesn't matter; the interpreter used to run the executable is defined in the &lt;a href="https://en.wikipedia.org/wiki/Shebang_(Unix)" rel="noopener noreferrer"&gt;shebang&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Link Three
&lt;/h3&gt;

&lt;p&gt;Git tags can be manually edited using &lt;code&gt;git tag&lt;/code&gt;, but you can install a custom command to help automate the process of implementing say, semantic versioning, by allowing you to specify "bump patch" or "bump major" and the tag is updated based on the current version.  &lt;/p&gt;

&lt;p&gt;You can get this functionality as &lt;code&gt;git version-bump&lt;/code&gt; if you &lt;code&gt;gem install git-version-bump&lt;/code&gt; to automate &lt;a href="https://github.com/mpalmer/git-version-bump" rel="noopener noreferrer"&gt;semantic version git tag bumps&lt;/a&gt;. &lt;code&gt;git-version-bump&lt;/code&gt; happens to be implemented in Ruby, but that doesn't matter as long as you have Ruby installed on your operating system.&lt;/p&gt;




&lt;h3&gt;
  
  
  Link Four
&lt;/h3&gt;

&lt;p&gt;Git tags do appear in GitHub's web interface, but &lt;a href="https://docs.github.com/en/github/administering-a-repository/managing-releases-in-a-repository" rel="noopener noreferrer"&gt;Releases&lt;/a&gt; are a richer experience. They are associated to a git tag, but allow attaching artefacts, and releases appear on the top level page of a repo. &lt;/p&gt;

&lt;p&gt;You can automate publishing git tags as GitHub release by installing the &lt;code&gt;github-release&lt;/code&gt; &lt;a href="https://github.com/mpalmer/github-release" rel="noopener noreferrer"&gt;gem&lt;/a&gt;, which gives you the &lt;code&gt;git release&lt;/code&gt; command. This uploads local tags to GitHub, using a custom API token setup for you within the command on first execution (including asking for your OTP). &lt;code&gt;github-release&lt;/code&gt; is also written in Ruby.&lt;/p&gt;




&lt;h3&gt;
  
  
  Link Five
&lt;/h3&gt;

&lt;p&gt;GitHub Actions are a now public feature which makes continuous deployment an integrated feature of GitHub. When you setup an Action on a Python repo, it's suggested that you could setup an action to &lt;a href="https://docs.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries" rel="noopener noreferrer"&gt;publish to PyPI&lt;/a&gt;. This action is triggered when a GitHub release is created. Your PyPI username and password are stored as GitHub secrets. Within the scope of a running action, it is a git context. This was important from earlier.&lt;/p&gt;




&lt;h3&gt;
  
  
  Do you see where we're going with this?
&lt;/h3&gt;

&lt;p&gt;By setting up &lt;code&gt;versioneer&lt;/code&gt; to listen to git tags, and using &lt;code&gt;git-version-bump&lt;/code&gt; to automate semver bumps of git tags, then &lt;code&gt;github-release&lt;/code&gt; to convert git tags into GitHub Releases, which then trigger a GitHub Action to publish to PyPI, you can publish a new version of your package with just two git commands. &lt;/p&gt;

&lt;p&gt;To get this setup on your existing package, install the local dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;git-version-bump
gem &lt;span class="nb"&gt;install &lt;/span&gt;github-release
pip &lt;span class="nb"&gt;install &lt;/span&gt;versioneer
versioneer &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Versioneer will guide you through the installation&lt;/p&gt;

&lt;p&gt;And &lt;a href="https://docs.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries" rel="noopener noreferrer"&gt;add a GitHub action for the publication of your package on GitHub release&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Bonus Link One
&lt;/h3&gt;

&lt;p&gt;If you setup &lt;code&gt;versioneer&lt;/code&gt; and the GitHub Action, you can still trigger a new release by manually creating a GitHub Release from the website. Just be sure to &lt;code&gt;git pull&lt;/code&gt; the new tags when you go back to the command line. &lt;/p&gt;




&lt;h3&gt;
  
  
  Bonus Link Two
&lt;/h3&gt;

&lt;p&gt;You can follow the same release process for packages in other programming languages; you just need a way to query git tags into your package version declaration, and a workflow to perform publication. &lt;code&gt;git-version-bump&lt;/code&gt; itself &lt;a href="https://github.com/mpalmer/git-version-bump#in-your-ruby-code" rel="noopener noreferrer"&gt;handles the git tag issue for ruby&lt;/a&gt;. &lt;/p&gt;




&lt;h3&gt;
  
  
  Bonus Link Three
&lt;/h3&gt;

&lt;p&gt;If you're worried about installing random gems or ruby itself, you can first install &lt;a href="https://github.com/rbenv/rbenv" rel="noopener noreferrer"&gt;&lt;code&gt;rbenv&lt;/code&gt;&lt;/a&gt; (which &lt;code&gt;pyenv&lt;/code&gt; was originally forked from), then &lt;code&gt;rbenv install&lt;/code&gt; the latest version of ruby.  Extending git only works if the executable is in your PATH, so you don't also need to install &lt;a href="https://bundler.io/" rel="noopener noreferrer"&gt;&lt;code&gt;bundler&lt;/code&gt;&lt;/a&gt; just for this purpose.&lt;/p&gt;




&lt;h3&gt;
  
  
  Bonus Link Four (to be completed by the reader)
&lt;/h3&gt;

&lt;p&gt;If you don't want worry about Ruby, you are more than free to create an executable shell scripts with the same functionality. &lt;/p&gt;

&lt;p&gt;You could use a combination of &lt;code&gt;git describe&lt;/code&gt;, &lt;a href="https://github.com/fsaintjacques/semver-tool" rel="noopener noreferrer"&gt;&lt;code&gt;semver-tool&lt;/code&gt;&lt;/a&gt;, and &lt;code&gt;git tag&lt;/code&gt; to help with that part. It might even be a one-liner with enough pipes. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.github.com/en/rest/reference/repos#create-a-release" rel="noopener noreferrer"&gt;creation of a GitHub release&lt;/a&gt; is a bit more complicated, but nothing you couldn't handle, I'm sure :)&lt;/p&gt;




&lt;h3&gt;
  
  
  Resources that helped me write this piece
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.atlassian.com/git/articles/extending-git" rel="noopener noreferrer"&gt;Extending git&lt;/a&gt;, by Stefan Saasen, Atlassian. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/git/git" rel="noopener noreferrer"&gt;github.com/git/git&lt;/a&gt;, a mirror of the git source code, and a URL that never stops being amazing. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://glasnt.com/blog/streamlining-release-cycles-with-git-version-bumprelease" rel="noopener noreferrer"&gt;My own post on this topic from 2014&lt;/a&gt; from my Ruby days, which lead me to &lt;a href="https://github.com/mpalmer/git-version-bump/pull/2" rel="noopener noreferrer"&gt;my own closed pull request on &lt;code&gt;git-version-bump&lt;/code&gt;&lt;/a&gt;, wherein I was told the point &lt;a href="https://github.com/mpalmer/git-version-bump/pull/2#issuecomment-42386500" rel="noopener noreferrer"&gt;"is to take this version information out of the filesystem"&lt;/a&gt;, which lead me to find versioneer by searching "setup.py version from git tags" which sent me to &lt;a href="https://stackoverflow.com/a/35589341/124019" rel="noopener noreferrer"&gt;this StackOverflow answer&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was updated in August 2021 to ensure the line links to &lt;code&gt;git&lt;/code&gt; source code were locked to a commit, and did not move over time.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>python</category>
    </item>
  </channel>
</rss>
