<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Engineering</title><link>https://www.pulumi.com/blog/category/engineering/</link><description>Pulumi blog posts: Engineering.</description><language>en-us</language><pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate><item><title>How We Eliminated Long-Lived CI Secrets Across 70+ Repos</title><link>https://www.pulumi.com/blog/eliminating-ci-secrets-with-pulumi-esc/</link><pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/eliminating-ci-secrets-with-pulumi-esc/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/eliminating-ci-secrets-with-pulumi-esc/index.png" /&gt;
&lt;p&gt;Supply chain attacks on CI/CD pipelines are accelerating. A growing pattern involves attackers compromising popular &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt; through &lt;em&gt;tag poisoning&lt;/em&gt; — rewriting trusted version tags to point to malicious code that harvests environment variables, cloud credentials, and API tokens from runner environments. The stolen credentials are then exfiltrated to attacker-controlled infrastructure, often before anyone notices.&lt;/p&gt;
&lt;p&gt;For every engineering organization, the question is no longer &lt;em&gt;if&lt;/em&gt; your CI pipeline will encounter a compromised dependency, but &lt;em&gt;what is exposed when it does&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;At Pulumi, we asked ourselves that question and decided the answer should be &amp;ldquo;nothing useful.&amp;rdquo; Here&amp;rsquo;s how we got there.&lt;/p&gt;
&lt;h2 id="the-problem-with-static-ci-secrets"&gt;The problem with static CI secrets&lt;/h2&gt;
&lt;p&gt;Most organizations store long-lived cloud credentials, API tokens, and service account keys as GitHub repository or organization secrets. But this approach has several well-known problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Broad availability.&lt;/strong&gt; Every workflow run on a repository can access every secret stored in that repo. A compromised action in any workflow can read them all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No expiration.&lt;/strong&gt; Secrets persist until someone manually rotates them. If exfiltrated, they give attackers persistent access for weeks or months.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No granular audit trail.&lt;/strong&gt; GitHub tells you a secret was used, but not which workflow, which step, or what it was used for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret sprawl.&lt;/strong&gt; Across dozens or hundreds of repos, the same credentials are often duplicated, making rotation a coordinated, error-prone effort.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a supply chain attack scenario, this is exactly what attackers count on: a single compromised action that can dump a trove of long-lived credentials.&lt;/p&gt;
&lt;h2 id="our-approach-zero-static-secrets"&gt;Our approach: zero static secrets&lt;/h2&gt;
&lt;p&gt;We replaced every static GitHub Secret across our CI pipelines with short-lived, dynamically fetched credentials using &lt;a href="https://www.pulumi.com/docs/esc/"&gt;Pulumi ESC&lt;/a&gt; and &lt;a href="https://openid.net/developers/how-connect-works/"&gt;OpenID Connect (OIDC)&lt;/a&gt;. The credential flow works in layers, each scoped and ephemeral:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitHub generates a short-lived OIDC token&lt;/strong&gt; scoped to the specific workflow run, repository, and branch. This token is cryptographically signed by GitHub&amp;rsquo;s OIDC provider.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The token is exchanged with Pulumi Cloud&lt;/strong&gt; for a short-lived Pulumi access token. Pulumi Cloud validates the OIDC claims (organization, repository, branch) against a configured trust policy before issuing the token.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Pulumi access token opens an ESC environment&lt;/strong&gt; to retrieve the credentials the workflow needs — cloud provider keys, API tokens, or other secrets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud credentials themselves are dynamic.&lt;/strong&gt; ESC environments use &lt;a href="https://www.pulumi.com/docs/esc/guides/configuring-oidc/"&gt;OIDC login providers&lt;/a&gt; to fetch short-lived credentials directly from AWS, Azure, or GCP. No static keys or cloud credentials are stored anywhere.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;a href="https://github.com/marketplace/actions/esc-action"&gt;&lt;code&gt;pulumi/esc-action&lt;/code&gt;&lt;/a&gt; GitHub Action handles this entire flow in a single workflow step.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
sequenceDiagram
participant Runner as GitHub Actions Runner
participant GH as GitHub OIDC Provider
participant PC as Pulumi Cloud
participant ESC as Pulumi ESC
participant Cloud as Cloud Provider (AWS/Azure/GCP)
Runner-&amp;gt;&amp;gt;GH: Request OIDC token (scoped to workflow run)
GH--&amp;gt;&amp;gt;Runner: Short-lived JWT
Runner-&amp;gt;&amp;gt;PC: Exchange JWT for Pulumi access token
PC--&amp;gt;&amp;gt;Runner: Short-lived access token
Runner-&amp;gt;&amp;gt;ESC: Open environment with access token
ESC-&amp;gt;&amp;gt;Cloud: OIDC login (assume role / federated identity)
Cloud--&amp;gt;&amp;gt;ESC: Short-lived cloud credentials
ESC--&amp;gt;&amp;gt;Runner: Cloud credentials + secrets
Note over Runner,Cloud: Nothing is stored. Everything expires.
&lt;/pre&gt;
&lt;h2 id="what-the-change-looks-like"&gt;What the change looks like&lt;/h2&gt;
&lt;p&gt;Before this migration, our workflows referenced static secrets stored in GitHub:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After the migration, an &lt;a href="https://www.pulumi.com/docs/esc/environments/"&gt;ESC environment&lt;/a&gt; handles credential fetching via OIDC. Here is what the environment definition looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::aws-login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1h&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;arn:aws:iam::123456789012:role/pulumi-esc-role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sessionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;esc-${context.pulumi.user.login}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Optional: scope down the session beyond what the role allows&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;policyArns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;arn:aws:iam::123456789012:policy/ci-build-minimal&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environmentVariables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${aws.login.accessKeyId}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${aws.login.secretAccessKey}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;roleArn&lt;/code&gt; and optional &lt;a href="https://www.pulumi.com/docs/esc/integrations/dynamic-login-credentials/aws-login/"&gt;&lt;code&gt;policyArns&lt;/code&gt;&lt;/a&gt; make least-privilege straightforward: each login provider assumes a specific role, and &lt;code&gt;policyArns&lt;/code&gt; can scope the session down further. You can use multiple login providers in one environment or separate environments per workflow to match permissions to each job&amp;rsquo;s needs.&lt;/p&gt;
&lt;p&gt;The workflow itself becomes minimal — a single step that authenticates via OIDC and injects the credentials:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Required for OIDC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Fetch secrets from ESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pulumi/esc-action@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;your-organization&amp;gt;/&amp;lt;your-esc-env&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The static &lt;code&gt;secrets.*&lt;/code&gt; references are gone entirely. Every credential is fetched at runtime through ESC.&lt;/p&gt;
&lt;h2 id="scale-70-repos-zero-static-secrets"&gt;Scale: 70+ repos, zero static secrets&lt;/h2&gt;
&lt;p&gt;We didn&amp;rsquo;t do this for one or two flagship repos; we rolled it out across &lt;strong&gt;every Pulumi provider repository&lt;/strong&gt;: AWS, Azure, GCP, Kubernetes, and over 60 more. The migration was managed centrally through our &lt;a href="https://github.com/pulumi/ci-mgmt"&gt;&lt;code&gt;ci-mgmt&lt;/code&gt;&lt;/a&gt; tooling, which generates consistent workflow configurations across all provider repos.&lt;/p&gt;
&lt;p&gt;The pattern is the same everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each repo has a corresponding ESC environment under a &lt;code&gt;github-secrets/&lt;/code&gt; project.&lt;/li&gt;
&lt;li&gt;All workflow-level &lt;code&gt;${{ secrets.* }}&lt;/code&gt; references have been removed.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;pulumi/esc-action&lt;/code&gt; step with OIDC auth is the single entry point for all credentials.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When every repo follows the same pattern like this, security posture is much more easily verifiable and auditable.&lt;/p&gt;
&lt;h2 id="auditability-and-centralized-control"&gt;Auditability and centralized control&lt;/h2&gt;
&lt;p&gt;Beyond eliminating static secrets, this migration gave us centralized visibility and control that GitHub Secrets cannot provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/esc/administration/audit-logs/"&gt;Audit logging&lt;/a&gt;.&lt;/strong&gt; ESC records which credentials were accessed, when, and by which workflow. This is a meaningful improvement over GitHub&amp;rsquo;s binary &amp;ldquo;secret was used&amp;rdquo; signal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Centralized access policies.&lt;/strong&gt; Access rules are defined once in ESC rather than scattered across individual repository settings pages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single-point rotation.&lt;/strong&gt; Because ESC environments can &lt;a href="https://www.pulumi.com/docs/esc/environments/imports/"&gt;import other environments&lt;/a&gt;, shared credentials live in a common base that all 70+ repo environments are composed of. Update it once, and every repo picks up the change on its next run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic credentials by default.&lt;/strong&gt; For cloud providers like AWS, Azure, and GCP, ESC fetches credentials via OIDC at open time. There is nothing to rotate because nothing is stored.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-happens-if-a-github-action-is-compromised"&gt;What happens if a GitHub Action is compromised&lt;/h2&gt;
&lt;p&gt;With this architecture in place, here is what an attacker gets if a compromised GitHub Action runs in our CI:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No GitHub Secrets to dump.&lt;/strong&gt; The repository settings page has no stored secrets for a malicious action to exfiltrate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OIDC tokens are scoped and short-lived.&lt;/strong&gt; The GitHub-issued JWT is valid only for the specific workflow run and expires within minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud credentials are ephemeral.&lt;/strong&gt; Any AWS, Azure, or GCP credentials fetched through ESC are short-lived and scoped to the role assumed during that run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No persistent access.&lt;/strong&gt; There are no long-lived tokens to reuse hours or days later.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Compare this to the traditional model, where a single compromised action could exfiltrate AWS access keys that remain valid until someone manually rotates them — which could be weeks or months.&lt;/p&gt;
&lt;p&gt;The goal is not to prevent every possible attack. It is to make the blast radius as small as possible when something goes wrong.&lt;/p&gt;
&lt;h2 id="get-started"&gt;Get started&lt;/h2&gt;
&lt;p&gt;If you want to adopt the same pattern in your own CI pipelines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/tutorials/esc-github/"&gt;&lt;strong&gt;Tutorial: Using ESC with GitHub Actions&lt;/strong&gt;&lt;/a&gt; — Step-by-step setup guide.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/blog/announcing-pulumi-esc-github-action/"&gt;&lt;strong&gt;Announcing the Pulumi ESC GitHub Action&lt;/strong&gt;&lt;/a&gt; — Full feature overview and capabilities.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/esc/guides/configuring-oidc/"&gt;&lt;strong&gt;Configuring OIDC for ESC&lt;/strong&gt;&lt;/a&gt; — Set up OIDC trust between ESC and your cloud providers.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/esc/"&gt;&lt;strong&gt;Pulumi ESC documentation&lt;/strong&gt;&lt;/a&gt; — Full documentation for environments, secrets, and configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your CI secrets do not have to be a liability. With OIDC and Pulumi ESC, they do not have to exist at all.&lt;/p&gt;</description><author>Boris Schlosser</author><category>esc</category><category>security</category><category>github-actions</category><category>continuous-delivery</category></item><item><title>How We Built a Distributed Work Scheduling System for Pulumi Cloud</title><link>https://www.pulumi.com/blog/how-we-built-a-distributed-work-scheduling-system-for-pulumi-cloud/</link><pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/how-we-built-a-distributed-work-scheduling-system-for-pulumi-cloud/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/how-we-built-a-distributed-work-scheduling-system-for-pulumi-cloud/index.png" /&gt;
&lt;p&gt;Pulumi Cloud orchestrates a growing number of workflow types: &lt;a href="https://www.pulumi.com/docs/deployments/"&gt;Deployments&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/insights/"&gt;Insights&lt;/a&gt; discovery scans, and &lt;a href="https://www.pulumi.com/docs/insights/policy/"&gt;policy evaluations&lt;/a&gt;. Some of that work runs on Pulumi&amp;rsquo;s infrastructure, and some of it runs on yours via &lt;a href="https://www.pulumi.com/docs/deployments/deployments/customer-managed-agents/"&gt;customer-managed workflow runners&lt;/a&gt;. We needed a scheduling system that could handle all of these workflow types reliably across both environments. In this post, we&amp;rsquo;ll take a look at the system we built.&lt;/p&gt;
&lt;h2 id="where-we-started"&gt;Where we started&lt;/h2&gt;
&lt;p&gt;For our first workflow integration, Deployments, scheduling wasn&amp;rsquo;t too complicated. A deployment was queued, a worker picked it up, and it ran. The queue was purpose-built for deployments, and it worked well for that single use case. Over time, we added more sophisticated logic to handle retries, ordering, rate limiting, observability, and more.&lt;/p&gt;
&lt;p&gt;With the launch of Insights, the number of workflow types grew. Now Pulumi Cloud manages discovery scans to catalog cloud resources and runs audit policy evaluations to continuously verify compliance. While these workflows share similarities, each type needed its own scheduling, retry logic, and failure handling.&lt;/p&gt;
&lt;p&gt;Later we added the option for customers to run workflows on their own infrastructure using &lt;a href="https://www.pulumi.com/docs/deployments/deployments/customer-managed-agents/"&gt;customer-managed workflow runners&lt;/a&gt;. As the complexity of these requirements grew, we knew that our initial approach for Deployments wasn&amp;rsquo;t going to scale. We needed a single system that could schedule any type of work, route it to the right place, and handle the messy reality of distributed execution: crashes, network failures, rate limits, and retries.&lt;/p&gt;
&lt;p&gt;We call this the &lt;strong&gt;background activity system&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="why-not-use-an-off-the-shelf-queue"&gt;Why not use an off-the-shelf queue?&lt;/h2&gt;
&lt;p&gt;Why build this instead of using Amazon SQS, RabbitMQ, or one of the many existing queue libraries? We considered these options but chose to build our own for a few reasons.&lt;/p&gt;
&lt;p&gt;Pulumi Cloud supports &lt;a href="https://www.pulumi.com/docs/administration/self-hosting/"&gt;self-hosted installations&lt;/a&gt;, including air-gapped environments. We intentionally minimize external dependencies so that self-hosted customers don&amp;rsquo;t have to stand up additional infrastructure. A system built on an external queue works fine for our hosted service, but it means self-hosted customers would need to provide a compatible backend. By building on top of the database we already require, we avoid adding another system to maintain.&lt;/p&gt;
&lt;p&gt;More importantly, queueing is only part of the problem. What we actually need is &lt;em&gt;scheduling with durability&lt;/em&gt;. This means ensuring that remote workers don&amp;rsquo;t lose activities on restart, priority so that urgent work gets compute resources first, constraints like &amp;ldquo;only so many scans per org at a time,&amp;rdquo; structured logging for observability, and checkpointing so that long-running operations can resume after a failure.&lt;/p&gt;
&lt;p&gt;These features can be layered onto a generic queue library but can require more code than implementing them directly. For example, priority queues are often implemented with multiple ranked queues, but this breaks single-activity-at-a-time constraints. A second queue wouldn&amp;rsquo;t see a job already running in the first one. There&amp;rsquo;s no way for producers in a distributed system to coordinate across the queues without support in the queuing system itself.&lt;/p&gt;
&lt;p&gt;Capacity management is another area where generic queues fall short. Distributed systems need to respond dynamically to slowdowns, network interruptions, and rate limits from downstream services. These are common low-level details that every workflow type needs, and building them into the scheduling layer means individual handlers don&amp;rsquo;t have to solve them independently.&lt;/p&gt;
&lt;p&gt;We also need structured logging that works everywhere, including on customer-managed runners behind firewalls where centralized logging services aren&amp;rsquo;t accessible.&lt;/p&gt;
&lt;p&gt;Building this ourselves gave us a system that works with existing infrastructure and handles these requirements natively.&lt;/p&gt;
&lt;h2 id="design-constraints"&gt;Design constraints&lt;/h2&gt;
&lt;p&gt;With that context, here are some of the constraints that shaped the design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pull-only agents.&lt;/strong&gt; Customer-managed workflow runners live behind NATs, corporate proxies, and air-gapped networks. They can&amp;rsquo;t accept inbound connections, so all communication has to be agent-initiated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixed execution environments.&lt;/strong&gt; The same system needs to work for Pulumi-hosted workers (with direct access to internal systems) and customer-managed runners (communicating entirely over REST). We didn&amp;rsquo;t want to maintain two separate code paths.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Different workflow types.&lt;/strong&gt; Deployments, Insights scans, and audit policy evaluations have different payloads and execution semantics, but they all need the same scheduling guarantees: exactly-once execution, automatic retries, failure recovery, and observability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic fault tolerance.&lt;/strong&gt; Agents crash, networks drop, and machines get recycled by autoscalers. The system needs to detect these failures and recover without needing a person to step in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensibility.&lt;/strong&gt; We knew we&amp;rsquo;d keep adding workflow types. Adding a new one should mean writing a handler and registering it, not building new infrastructure.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-background-activity"&gt;The background activity&lt;/h2&gt;
&lt;p&gt;At the center of the system is the &lt;strong&gt;background activity&lt;/strong&gt;, a persistent, typed work unit. Each activity includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;type discriminator&lt;/strong&gt; that identifies what kind of work it represents (e.g., &amp;ldquo;insights-discovery&amp;rdquo; or &amp;ldquo;policy-evaluation&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;payload&lt;/strong&gt; specific to that type, containing whatever data the handler needs&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;routing context&lt;/strong&gt; that determines which runner pool should execute it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scheduling metadata&lt;/strong&gt; like priority, activation time, and retry configuration&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;status&lt;/strong&gt; tracking where the activity is in its lifecycle&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The type discriminator makes this system polymorphic. The scheduling engine doesn&amp;rsquo;t need to know what&amp;rsquo;s inside the payload. It moves activities through their lifecycle and delegates the actual work to a type-specific handler.&lt;/p&gt;
&lt;h3 id="the-state-machine"&gt;The state machine&lt;/h3&gt;
&lt;p&gt;Every activity follows the same lifecycle regardless of type:&lt;/p&gt;
&lt;pre class="mermaid"&gt;
---
config:
flowchart:
curve: linear
---
graph LR
Start(( )) --&amp;gt;|Created| Ready
Ready --&amp;gt;|Leased| Pending
Pending --&amp;gt;|Started| Executing
Executing --&amp;gt;|Success| Completed
Completed --&amp;gt; End(( ))
Executing --&amp;gt;|Error| Failed
Failed --&amp;gt; End
Executing --&amp;gt;|Canceled| Canceled
Canceled --&amp;gt; End
Executing --&amp;gt;|Dependencies| Waiting
Waiting --&amp;gt;|Unblocked| Ready
Pending --&amp;gt;|Lease expired| Restarting
Executing --&amp;gt;|Lease expired| Restarting
Restarting --&amp;gt;|Re-lease| Ready
style Start fill:#000,stroke:#000,color:#000
style End fill:#000,stroke:#000,color:#000
&lt;/pre&gt;
&lt;p&gt;The states fall into two groups:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Running states&lt;/strong&gt; (work is in flight or can be resumed):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ready&lt;/strong&gt;: queued and eligible to be claimed by a worker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pending&lt;/strong&gt;: claimed by a worker, execution about to start&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Executing&lt;/strong&gt;: actively running on a worker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Waiting&lt;/strong&gt;: parked, blocked on one or more dependency activities&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restarting&lt;/strong&gt;: recovered after a worker failure, ready to be re-claimed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Terminal states&lt;/strong&gt; (work is done):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Completed&lt;/strong&gt;, &lt;strong&gt;Failed&lt;/strong&gt;, &lt;strong&gt;Canceled&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New workflow types get these features automatically: scheduling, retries, dependency management, and observability.&lt;/p&gt;
&lt;h2 id="leases-distributed-execution-without-coordination"&gt;Leases: distributed execution without coordination&lt;/h2&gt;
&lt;p&gt;A central challenge of any distributed work queue is preventing double-execution. If two agents try to execute the same activity simultaneously, you get duplicate work and data corruption. A central coordinator can solve this, but it becomes a single point of failure.&lt;/p&gt;
&lt;p&gt;We use lease-based optimistic concurrency instead. This is a well-known pattern, adapted here for long-running, stateful workflows.&lt;/p&gt;
&lt;h3 id="how-it-works"&gt;How it works&lt;/h3&gt;
&lt;p&gt;When an agent is ready for new work, it asks the service to &lt;strong&gt;lease&lt;/strong&gt; an activity. The service atomically selects the highest-priority ready activity, assigns a lease token with an expiration time, and transitions the activity to &lt;code&gt;Pending&lt;/code&gt;. No other agent can claim the same activity.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
sequenceDiagram
participant Agent
participant Service
Agent-&amp;gt;&amp;gt;Service: Poll for work
Note right of Service: Select highest-priority&amp;lt;br/&amp;gt;Ready activity
Note right of Service: Atomically set lease&amp;lt;br/&amp;gt;token + expiration
Service-&amp;gt;&amp;gt;Agent: Lease (token, expiration)
Agent-&amp;gt;&amp;gt;Service: Begin execution
Note right of Service: Transition to Executing
Note over Agent: Work in progress...
Agent-&amp;gt;&amp;gt;Service: Renew lease
Service-&amp;gt;&amp;gt;Agent: New expiration
Note over Agent: Work continues...
Agent-&amp;gt;&amp;gt;Service: Complete (token, result)
Note right of Service: Transition to Completed&amp;lt;br/&amp;gt;Archive activity
Service-&amp;gt;&amp;gt;Agent: Acknowledged
&lt;/pre&gt;
&lt;p&gt;While executing, the agent periodically &lt;strong&gt;renews&lt;/strong&gt; its lease to signal that it&amp;rsquo;s still working. If the agent crashes, loses network connectivity, or is terminated, it stops renewing. Once the lease expires, the service transitions the activity to &lt;code&gt;Restarting&lt;/code&gt;, making it available for another agent to claim.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
sequenceDiagram
participant A as Agent A
participant S as Service
participant B as Agent B
A-&amp;gt;&amp;gt;S: Lease activity
S-&amp;gt;&amp;gt;A: Token + expiration
Note over A: Executing...
A--xS: Agent A crashes
Note right of S: Lease expires
Note right of S: Transition → Restarting
B-&amp;gt;&amp;gt;S: Poll for work
Note right of S: Lease to Agent B&amp;lt;br/&amp;gt;Transition → Pending
S-&amp;gt;&amp;gt;B: Token + expiration
Note over B: Agent B continues execution
&lt;/pre&gt;
&lt;p&gt;The service doesn&amp;rsquo;t need to explicitly coordinate between workers because leases are acquired using atomic database operations. The lease expiration is the failure detector; if a lease expires, then the work needs to be rescheduled.&lt;/p&gt;
&lt;h2 id="routing-work-to-the-right-runner-pool"&gt;Routing work to the right runner pool&lt;/h2&gt;
&lt;p&gt;Pulumi Cloud supports multiple &lt;a href="https://www.pulumi.com/docs/deployments/deployments/customer-managed-agents/"&gt;workflow runner pools&lt;/a&gt;. An organization might have one pool for production in &lt;code&gt;us-east-1&lt;/code&gt;, another for staging in &lt;code&gt;eu-west-1&lt;/code&gt;, and use Pulumi-hosted runners for development. Work needs to reach the right pool.&lt;/p&gt;
&lt;p&gt;Each activity carries a &lt;strong&gt;routing context&lt;/strong&gt; that identifies which runner pool should execute it. When a runner polls for work, it filters by its own pool identifier so that it only sees activities meant for it.&lt;/p&gt;
&lt;p&gt;We use prefix matching for this filtering. A runner matches activities whose context starts with its pool&amp;rsquo;s identifier. This means the service can use hierarchical contexts (e.g., &lt;code&gt;pool-abc/insights/scan-123&lt;/code&gt;) and runners will still match on the pool prefix. Cleanup is also straightforward; when a runner pool is deleted, all activities with that context prefix are bulk-canceled.&lt;/p&gt;
&lt;p&gt;This routing mechanism works the same way regardless of workflow type, and adding a new workflow type doesn&amp;rsquo;t require changes to the routing layer.&lt;/p&gt;
&lt;h2 id="dependencies-and-multi-step-workflows"&gt;Dependencies and multi-step workflows&lt;/h2&gt;
&lt;p&gt;Some workflows are naturally multi-step. An Insights discovery scan might discover resources that then need policy evaluation. Rather than building a separate orchestration engine, we built dependency management into the activity system.&lt;/p&gt;
&lt;p&gt;An activity can declare a &lt;strong&gt;dependency set&lt;/strong&gt;: a list of other activities that must complete before it can run. A dependent activity enters the &lt;code&gt;Waiting&lt;/code&gt; state when created. As its dependencies complete, the system checks whether all prerequisites are satisfied. When the last one finishes, the waiting activity transitions to &lt;code&gt;Ready&lt;/code&gt; and enters the scheduling queue.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
graph TD
A[&amp;#34;Insights Discovery&amp;lt;br/&amp;gt;&amp;lt;b&amp;gt;Executing&amp;lt;/b&amp;gt;&amp;#34;] --&amp;gt;|depends on| B[&amp;#34;Policy Evaluation&amp;lt;br/&amp;gt;&amp;lt;b&amp;gt;Waiting&amp;lt;/b&amp;gt;&amp;#34;]
A --&amp;gt;|completes| C[&amp;#34;Insights Discovery&amp;lt;br/&amp;gt;&amp;lt;b&amp;gt;Completed&amp;lt;/b&amp;gt;&amp;#34;]
C --&amp;gt;|triggers| D[&amp;#34;Policy Evaluation&amp;lt;br/&amp;gt;&amp;lt;b&amp;gt;Ready&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt;(auto-scheduled)&amp;#34;]
&lt;/pre&gt;
&lt;p&gt;This gives us a lightweight &lt;a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph"&gt;DAG&lt;/a&gt; of work without requiring a separate workflow engine. Dependent activities get the same guarantees as any other activity: lease-based execution, automatic recovery, and observability.&lt;/p&gt;
&lt;h2 id="two-execution-modes-one-interface"&gt;Two execution modes, one interface&lt;/h2&gt;
&lt;p&gt;This is where the design really pays off for customer-managed runners. The system supports two execution modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Direct mode&lt;/strong&gt; runs in-process alongside the Pulumi Cloud service. Workers have low-latency access to internal systems and can process activities with minimal overhead. This is what Pulumi-hosted runners use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remote mode&lt;/strong&gt; communicates over REST APIs. The runner polls for activities, leases them, executes work locally, and reports results back over HTTP. This is what customer-managed runners use. No database access, no internal network access, no inbound connectivity required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both modes share the same handler interface so that a workflow handler doesn&amp;rsquo;t need to know where it&amp;rsquo;s running. Whether it&amp;rsquo;s running on Pulumi&amp;rsquo;s hosted infrastructure or on a customer&amp;rsquo;s Kubernetes cluster, the handler simply processes the payload and reports a result.&lt;/p&gt;
&lt;h2 id="putting-it-all-together"&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s walk through a concrete example. A user wants to run an Insights discovery scan on an AWS account using a customer-managed workflow runner.&lt;/p&gt;
&lt;pre class="mermaid"&gt;
sequenceDiagram
participant User
participant PC as Pulumi Cloud
participant Runner as Workflow Runner&amp;lt;br/&amp;gt;(Customer Infrastructure)
User-&amp;gt;&amp;gt;PC: 1. Configure Insights scan&amp;lt;br/&amp;gt;for runner pool
Note right of PC: 2. Create background activity&amp;lt;br/&amp;gt;type: insights-discovery&amp;lt;br/&amp;gt;context: runner-pool-xyz&amp;lt;br/&amp;gt;status: Ready
Runner-&amp;gt;&amp;gt;PC: 3. Poll for work (filtered by pool)
PC-&amp;gt;&amp;gt;Runner: 4. Lease activity (token + expiration)
Runner-&amp;gt;&amp;gt;PC: 5. Initialize workflow
PC-&amp;gt;&amp;gt;Runner: Return cloud credentials + job token
Note over Runner: 6. Execute scan locally&amp;lt;br/&amp;gt;(talks directly to cloud APIs —&amp;lt;br/&amp;gt;credentials are used only on&amp;lt;br/&amp;gt;the runner)
Runner-&amp;gt;&amp;gt;PC: 7. Renew lease
Note right of PC: Extend expiration
Runner-&amp;gt;&amp;gt;PC: 8. Report completion
Note right of PC: 9. Mark completed&amp;lt;br/&amp;gt;Archive activity
Note right of PC: 10. Unblock dependent activities&amp;lt;br/&amp;gt;(e.g., policy evaluation)
&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;A user configures an AWS account for Insights scanning in Pulumi Cloud and assigns it to a workflow runner pool.&lt;/li&gt;
&lt;li&gt;Pulumi Cloud creates a background activity with the type set to insights discovery, the routing context set to the runner pool, and the payload containing the account configuration.&lt;/li&gt;
&lt;li&gt;A customer-managed workflow runner polling that pool detects new work.&lt;/li&gt;
&lt;li&gt;The runner leases the activity, acquiring an exclusive lock via the lease token.&lt;/li&gt;
&lt;li&gt;The runner initializes the workflow, receiving any required cloud provider credentials (e.g., resolved from &lt;a href="https://www.pulumi.com/docs/esc/"&gt;Pulumi ESC (Environments, Secrets, and Configuration)&lt;/a&gt;) and a job token from Pulumi Cloud.&lt;/li&gt;
&lt;li&gt;The runner executes the scan locally on the customer&amp;rsquo;s infrastructure, talking directly to the cloud provider APIs.&lt;/li&gt;
&lt;li&gt;During execution, the runner periodically renews its lease to signal liveness.&lt;/li&gt;
&lt;li&gt;The scan completes, and the runner reports the result back to Pulumi Cloud.&lt;/li&gt;
&lt;li&gt;The service marks the activity as completed and archives it.&lt;/li&gt;
&lt;li&gt;If a dependent policy evaluation activity was waiting on this scan, it automatically transitions to &lt;code&gt;Ready&lt;/code&gt; and enters the scheduling queue, where another runner in the pool can pick it up.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This flow works the same way whether the runner is hosted by Pulumi or by the customer. The only difference is whether the execution mode is direct or remote.&lt;/p&gt;
&lt;h2 id="retries-and-scheduling"&gt;Retries and scheduling&lt;/h2&gt;
&lt;p&gt;Failures are expected in distributed systems. The background activity system handles them at several levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lease expiration&lt;/strong&gt; covers hard failures like agent crashes, network partitions, and machine terminations. If a lease expires, the activity moves to &lt;code&gt;Restarting&lt;/code&gt;, and is available for another agent to pick up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handler-controlled retries&lt;/strong&gt; cover soft failures like transient API errors and rate limits. A handler can request a reschedule with a delay, putting the activity back in &lt;code&gt;Ready&lt;/code&gt; with a future activation time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic retries&lt;/strong&gt; provide a configurable retry budget per activity. Each activity can specify how many times it should be retried and the delay between attempts, preventing runaway retry loops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Priority scheduling&lt;/strong&gt; ensures urgent work gets processed first. Higher-priority activities are leased before lower-priority ones, even if the lower-priority activity has been waiting longer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lease renewal during slowdowns&lt;/strong&gt; keeps the activity alive without blocking other work, even if a downstream service is slow. The agent continues renewing its lease while it waits, and the scheduler remains free to assign other activities to other agents.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="observability"&gt;Observability&lt;/h2&gt;
&lt;p&gt;Every activity generates a structured log of its execution, including timestamps, severity levels, and code context. Logs are stored with the activity record and are accessible via an API and admin tooling.&lt;/p&gt;
&lt;p&gt;This is especially useful for customer-managed runners, where the service can&amp;rsquo;t directly observe the execution environment. The structured log gives operators visibility into the execution context, even when the runner is behind a firewall. Handlers can also use these logs as a progress journal, encoding checkpoints that allow a restarted activity to pick up where it left off rather than starting from scratch.&lt;/p&gt;
&lt;p&gt;Retention policies are configurable per organization and per workflow type. Completed activities can be retained for auditing or purged to manage storage, and failed activities are typically retained longer for debugging.&lt;/p&gt;
&lt;h2 id="what-we-learned"&gt;What we learned&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A generic system pays off quickly.&lt;/strong&gt; Our initial instinct was to build targeted solutions for each workflow type. Investing in a generic activity system required more upfront design work, but now adding a new workflow type requires a fraction of the effort it would take otherwise. New workflows ship with full scheduling, retry, and observability support from day one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Leases handle many failure modes.&lt;/strong&gt; We evaluated several approaches for distributed work coordination, including message queues with explicit acknowledgment and coordinator-based assignment. The lease model works well because all failure modes are handled through timeouts. If an agent is running as expected, it renews. If it isn&amp;rsquo;t, the lease expires.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keeping the execution paths symmetric requires discipline.&lt;/strong&gt; Making the hosted and self-hosted paths share the same handler interface was a deliberate choice. It would be easy to add shortcuts for the hosted path that bypass the remote API, but resisting that temptation means that features work for both cases automatically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The hard part isn&amp;rsquo;t running the work.&lt;/strong&gt; Running a scan or a deployment is straightforward once you have the right credentials. The real complexity is in everything around the execution: scheduling, routing, leasing, retrying, resolving dependencies, and cleaning up. These operational concerns aren&amp;rsquo;t visible to users, but they are essential to providing a reliable experience.&lt;/p&gt;
&lt;h2 id="wrapping-it-up"&gt;Wrapping it up&lt;/h2&gt;
&lt;p&gt;Today this system powers deployments, Insights discovery scans, and policy evaluations across both Pulumi Cloud and customer-managed infrastructure. The architecture is general enough that every new workflow type we add inherits the full scheduling, routing, retry, and observability stack without additional plumbing.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in running workflows on your own infrastructure, check out &lt;a href="https://www.pulumi.com/docs/deployments/deployments/customer-managed-agents/"&gt;customer-managed workflow runners&lt;/a&gt;. To see how Insights can help you understand and manage your cloud infrastructure, &lt;a href="https://www.pulumi.com/docs/insights/"&gt;get started with Pulumi Insights&lt;/a&gt;.&lt;/p&gt;</description><author>Levi Blackstone</author><author>Davide Massarenti</author><category>pulumi-cloud</category><category>features</category><category>engineering</category><category>insights</category></item><item><title>How We Built Platybot: An AI-Powered Analytics Assistant</title><link>https://www.pulumi.com/blog/how-we-built-platybot-an-ai-powered-analytics-assistant/</link><pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/how-we-built-platybot-an-ai-powered-analytics-assistant/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/how-we-built-platybot-an-ai-powered-analytics-assistant/index.png" /&gt;
&lt;p&gt;Before Platybot, our #analytics Slack channel was a support queue. Every day, people from every team would ask questions: &amp;ldquo;Which customers use feature X?&amp;rdquo;, &amp;ldquo;What&amp;rsquo;s our ARR by plan type?&amp;rdquo;, &amp;ldquo;Do we have a report for template usage?&amp;rdquo; Our two-person data team was a bottleneck.&lt;/p&gt;
&lt;figure style="width: 50%; float: right; margin-left: 20px; margin-bottom: 10px;"&gt;
&lt;img src="data-request-inbox.jpeg" alt="A fictional Slack #analytics channel showing multiple employees asking data questions like report requests, ARR breakdowns, and active user numbers"&gt;
&lt;figcaption&gt;&lt;i&gt;Our #analytics channel, before Platybot (dramatized).&lt;/i&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We didn&amp;rsquo;t want to just throw an LLM at our &lt;a href="https://www.pulumi.com/case-studies/snowflake/"&gt;Snowflake&lt;/a&gt; warehouse either. Without guardrails, large language models generate SQL that may work but silently gets the answer wrong. Different join logic, wrong filters, missing snapshot handling, incorrect summarization. We needed something that could answer reliably for most queries, otherwise we&amp;rsquo;d switch to fixing LLM SQL queries.&lt;/p&gt;
&lt;p&gt;So we built Platybot (platypus + bot, named after our mascot), an AI-powered analytics assistant that any Pulumi employee can use to query our Data Warehouse in natural language. It&amp;rsquo;s available as a Web App, a Slack bot, and a &lt;a href="https://modelcontextprotocol.io/"&gt;Model Context Protocol (MCP)&lt;/a&gt; server. The infrastructure is deployed with Pulumi IaC (Infrastructure as Code). But the most important thing we learned building it is that the AI was the easy part. The semantic layer is what makes it work.&lt;/p&gt;
&lt;h2 id="the-problem-with-throwing-ai-at-your-data-warehouse"&gt;The problem with throwing AI at your Data Warehouse&lt;/h2&gt;
&lt;p&gt;The naive solution is obvious: connect an LLM to your database and let it write SQL. But this fails in practice, and the failure mode is insidious. Consider a few examples from our warehouse:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ARR is a snapshot metric.&lt;/strong&gt; If you query ARR (Annual Recurring Revenue) without filtering by end-of-period dates (last day of month or quarter), you get duplicate rows and wildly inflated numbers. An LLM doesn&amp;rsquo;t automatically know this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Account queries need exclusions.&lt;/strong&gt; Most queries should exclude Pulumi&amp;rsquo;s own internal accounts and deleted ones. Without these filters, you&amp;rsquo;re counting test data alongside real customers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User queries need employee filters.&lt;/strong&gt; Querying active users without excluding Pulumi employees inflates adoption metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The danger shows up when results feel decision-ready before anyone has validated how the numbers were derived. A confidently wrong ARR figure presented to leadership is worse than no answer at all.&lt;/p&gt;
&lt;p&gt;Many organizations run into the same constraint once data usage spreads. Dashboards answer yesterday&amp;rsquo;s questions, not today&amp;rsquo;s. Ad-hoc LLM queries answer today&amp;rsquo;s questions, but incorrectly. The gap between &amp;ldquo;I have a question&amp;rdquo; and &amp;ldquo;I have a trustworthy answer&amp;rdquo; is where data teams can get stuck.&lt;/p&gt;
&lt;h2 id="why-we-built-a-semantic-layer-first"&gt;Why we built a semantic layer first&lt;/h2&gt;
&lt;p&gt;Before writing a single line of AI code, we built a semantic layer using &lt;a href="https://cube.dev/"&gt;Cube&lt;/a&gt; (open source). This was the hardest, least glamorous, and most important part of the entire project.&lt;/p&gt;
&lt;p&gt;A semantic layer is a shared, versioned definition of what your business metrics mean.&lt;/p&gt;
&lt;p&gt;“Monthly active users” starts with COUNT(DISTINCT user_id), but the aggregation is only the outer layer. It has to be applied to the right table (&lt;code&gt;fct_pulumi_operations&lt;/code&gt;), with the right filters (exclude Pulumi employees, exclude deleted organizations), scoped to a calendar month, and counting only users who performed real operations — not just previews. The semantic layer encodes all of this once, and the AI can use it to build queries without needing to guess which tables are related to each other, whether it&amp;rsquo;s one-to-one or many-to-many, etc.&lt;/p&gt;
&lt;p&gt;We organized our data into seven domains: Revenue, Cloud, Core, Clickstream, Community, Support, and People. Each domain contains cubes (think of them as well-defined, composable views) with explicit measures, dimensions, and joins. Here&amp;rsquo;s a real example from our Cloud domain (trimmed for readability):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;cubes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fct_pulumi_operations&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql_table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CLOUD.FCT_PULUMI_OPERATIONS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Pulumi CLI operations (update, preview, destroy, refresh).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Each row is one operation with resource changes, duration,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; and CLI environment.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;dim_organization&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{CUBE}.ORGANIZATION_HK = {dim_organization}.ORGANIZATION_HK&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;many_to_one&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;dim_stacks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{CUBE}.STACK_PROGRAM_HK = {dim_stacks}.STACK_PROGRAM_HK&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;many_to_one&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;dim_user&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{CUBE}.USER_HK = {dim_user}.USER_HK&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;many_to_one&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;resource_count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{CUBE}.RESOURCE_COUNT&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sum&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Number of resources active when the operation finished&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;operations_succeeded&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CASE WHEN {CUBE}.OPERATION_STATE = &amp;#39;succeeded&amp;#39; THEN 1 ELSE 0 END&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sum&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Count of operations that succeeded&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# ... plus other measures&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;operation_type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{CUBE}.OPERATION_TYPE&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Type of operation (Refresh, Update, Destroy, etc)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;operation_state&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{CUBE}.OPERATION_STATE&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Last known state of this operation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# ... 20+ more dimensions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The key insight: the semantic layer makes the AI&amp;rsquo;s job tractable. Instead of generating arbitrary SQL from scratch, where the search space is &amp;ldquo;any possible SQL query against hundreds of tables,&amp;rdquo; the AI picks from a defined set of measures, dimensions, and joins. The search space shrinks from almost infinite to a well-bounded set of valid combinations.&lt;/p&gt;
&lt;p&gt;A semantic layer is to an AI data assistant what a type system is to a programming language. It doesn&amp;rsquo;t eliminate errors, but it makes entire categories of mistakes structurally impossible. The AI can&amp;rsquo;t calculate ARR wrong because it doesn&amp;rsquo;t calculate ARR at all. It references a pre-defined measure that already encodes the correct logic.&lt;/p&gt;
&lt;p&gt;Building the semantic layer was mainly data engineering work. We already had agreed-upon metric definitions across the company. The challenge was encoding those definitions into Cube: specifying the correct joins between tables, wiring up the right filters, and making sure every measure matched the logic our dashboards already used. Tedious, but essential.&lt;/p&gt;
&lt;h2 id="adding-the-ai-layer"&gt;Adding the AI layer&lt;/h2&gt;
&lt;p&gt;With the semantic layer in place, the AI becomes a translation problem: convert natural language into a Cube query.&lt;/p&gt;
&lt;p&gt;Platybot supports multiple models: Claude Opus 4.6, Claude Sonnet 4.5, and Gemini 3 Pro. Users can choose which model to use. We found that different models have different strengths. Claude excels at structured data queries, while Gemini performs very well on text-heavy tasks like analyzing call transcriptions.&lt;/p&gt;
&lt;p&gt;The system prompt gives the model awareness of available cubes, their measures, dimensions, and joins. When a user asks &amp;ldquo;What&amp;rsquo;s the ARR breakdown by plan type?&amp;rdquo;, the model doesn&amp;rsquo;t write SQL. Instead, it constructs a Cube query, selecting the &lt;code&gt;total_arr&lt;/code&gt; measure from the ARR table and grouping by the &lt;code&gt;sku&lt;/code&gt; from the subscriptions dimension. Cube handles the SQL generation, the joins, and the filters. For edge cases the semantic layer doesn&amp;rsquo;t cover, the model can fall back to direct (read-only) SQL against Snowflake, but it may already have a basic query that is already close to what it needs.&lt;/p&gt;
&lt;p&gt;Query generation follows a workflow that maps to the same tools a human analyst would use:&lt;/p&gt;
&lt;pre class="mermaid"&gt;
flowchart TB
subgraph Entry[&amp;#34;Entry points&amp;#34;]
Web[&amp;#34;Web UI&amp;#34;]
Slack[&amp;#34;Slack (@platybot)&amp;#34;]
MCP[&amp;#34;MCP Server&amp;#34;]
end
subgraph Core[&amp;#34;Platybot core&amp;#34;]
BE[&amp;#34;Backend (Express + LLM)&amp;#34;]
end
subgraph Data[&amp;#34;Data layer&amp;#34;]
Cube[&amp;#34;Cube semantic layer&amp;#34;]
SF[&amp;#34;Snowflake&amp;#34;]
end
Web --&amp;gt; BE
Slack --&amp;gt; BE
MCP --&amp;gt; BE
BE --&amp;gt;|&amp;#34;Structured queries&amp;#34;| Cube
BE --&amp;gt;|&amp;#34;Fallback SQL (read-only)&amp;#34;| SF
Cube --&amp;gt; SF
&lt;/pre&gt;
&lt;p&gt;The workflow is: discover domains, explore cubes, understand the schema, construct and execute the query. This mirrors how a data analyst would work. You don&amp;rsquo;t jump straight to SQL; you first understand what data is available and what the metrics mean.&lt;/p&gt;
&lt;p&gt;The system&amp;rsquo;s role is narrower than people expect. It&amp;rsquo;s a translator between human intent and a well-defined data model, not a general-purpose data scientist. This is a feature, not a limitation. By constraining Platybot to operate within the semantic layer, we get reliability. The creativity goes into understanding the question, not into inventing SQL.&lt;/p&gt;
&lt;h2 id="meeting-users-where-they-are"&gt;Meeting users where they are&lt;/h2&gt;
&lt;p&gt;The backend solved query generation. It did not solve usage and if people don&amp;rsquo;t use it, it doesn&amp;rsquo;t matter. We launched Platybot across three interfaces, each designed for a different workflow.&lt;/p&gt;
&lt;h3 id="the-web-ui"&gt;The web UI&lt;/h3&gt;
&lt;p&gt;The web app is the primary interface: a React 19 + TypeScript + Vite + Tailwind conversational UI where employees can explore data through multi-turn conversations. It supports table visualization, data export, and conversation history so you can pick up where you left off.&lt;/p&gt;
&lt;p&gt;Every analysis produces a shareable report with a permanent link, protected behind company authentication. The report shows the agent&amp;rsquo;s reasoning so you can verify its approach, and lists every query and table used along with a sample of the data. Each query includes a direct link to run it in Metabase, our reporting tool, so any useful query can be saved, scheduled, or extended without starting from scratch.&lt;/p&gt;
&lt;p&gt;&lt;img src="platybot-report.png" alt="Screenshot of a Platybot report showing the AI&amp;rsquo;s reasoning process, an executed SQL query, and a link to open the query in Metabase"&gt;&lt;/p&gt;
&lt;p&gt;The web UI sees the highest adoption of all three access points. It&amp;rsquo;s where people go for deeper data exploration: multi-step analyses, follow-up questions, and comparing metrics across different dimensions.&lt;/p&gt;
&lt;p&gt;One fun detail we&amp;rsquo;ve added during our last hackathon: while queries iterate through analysis steps (discovering domains, exploring cubes, executing queries), users are entertained by a platypus-themed runner game. Think Chrome&amp;rsquo;s dinosaur game, but with our mascot on a skateboard. Sometimes Platybot can take a long time iterating, so you can try to beat your high score in the meantime!&lt;/p&gt;
&lt;p&gt;&lt;img src="platybot-web-ui.png" alt="Screenshot of the Platybot web interface showing a chat conversation on the left side and a platypus character on a skateboard in a runner-style minigame on the right, displayed while processing a query"&gt;&lt;/p&gt;
&lt;h3 id="slack"&gt;Slack&lt;/h3&gt;
&lt;p&gt;Platybot is also available as a Slack bot. Users @mention it in any channel, and it replies in a thread with the answer, keeping the channel clean while making results visible to the whole team. It&amp;rsquo;s best suited for quick lookups — &amp;ldquo;what&amp;rsquo;s the ARR for account X?&amp;rdquo; — where the answer fits in a message rather than a deep analysis. Most usage stayed in the web UI, but the Slack bot fills a different niche: answers that benefit from being shared in context.&lt;/p&gt;
&lt;p&gt;&lt;img src="platybot-slack.png" alt="Screenshot of a Slack thread where a user asks Platybot for the split between operation types in the last 30 days, and Platybot replies with a summary of 10.1 million operations broken down by type along with a bar chart"&gt;&lt;/p&gt;
&lt;h3 id="mcp-making-platybot-usable-by-agents"&gt;MCP: Making Platybot usable by Agents&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/"&gt;Model Context Protocol (MCP)&lt;/a&gt; server is the newest addition, launched February 4, 2026. It exposes six tools that any MCP-compatible client can use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;list_domains&lt;/code&gt; - discover available data domains&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list_cubes&lt;/code&gt; - explore cubes within a domain&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_cube_details&lt;/code&gt; - get measures, dimensions, and joins for a cube&lt;/li&gt;
&lt;li&gt;&lt;code&gt;execute_cube_query&lt;/code&gt; - run a structured query&lt;/li&gt;
&lt;li&gt;&lt;code&gt;execute_sql_query&lt;/code&gt; - raw SQL fallback (read-only)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_generated_sql&lt;/code&gt; - preview SQL without executing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Setup is a single command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude mcp add --transport http platybot &amp;lt;platybot-mcp-url&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a paradigm shift. Platybot goes from a destination (open the app, ask a question) to a capability that any AI tool can use. An engineer writing a postmortem can pull live metrics without leaving their terminal. An analyst building a report in Claude Code can query data inline. The Data Warehouse becomes ambient, always available, never in the way.&lt;/p&gt;
&lt;p&gt;Security uses OAuth 2.0 Device Authorization Flow with PKCE for CLI-friendly authentication, with @pulumi.com domain restriction, read-only enforcement, rate limiting, and full audit logging.&lt;/p&gt;
&lt;p&gt;A meta moment: we used the Platybot MCP server to query our Slack messages table to find the real usage data cited above.&lt;/p&gt;
&lt;h2 id="deploying-with-pulumi"&gt;Deploying with Pulumi&lt;/h2&gt;
&lt;p&gt;Platybot&amp;rsquo;s infrastructure runs on AWS and is managed entirely with Pulumi IaC. The stack includes ECS Fargate for the application, an Application Load Balancer for traffic routing, EFS for persistent storage, ECR for container images, and Route 53 for DNS. The setup is intentionally simple and we use a small instance size since it&amp;rsquo;s just for internal usage.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a representative snippet of the Fargate service definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;awsx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FargateService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;platybot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt;: &lt;span class="kt"&gt;cluster.arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;assignPublicIp&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taskDefinitionArgs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;platybot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;image&lt;/span&gt;: &lt;span class="kt"&gt;image.imageUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;essential&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;portMappings&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;containerPort&lt;/span&gt;: &lt;span class="kt"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;targetGroup&lt;/span&gt;: &lt;span class="kt"&gt;targetGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CUBE_API_URL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;cubeApiUrl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NODE_ENV&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;production&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ANTHROPIC_API_KEY&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;valueFrom&lt;/span&gt;: &lt;span class="kt"&gt;anthropicKeySecret.arn&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SNOWFLAKE_PASSWORD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;valueFrom&lt;/span&gt;: &lt;span class="kt"&gt;snowflakePasswordSecret.arn&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;logConfiguration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;logDriver&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;awslogs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;awslogs-group&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;awslogs-region&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;awslogs-stream-prefix&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;platybot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dogfooding Pulumi for our internal tools has real benefits beyond the obvious. Staging environments are trivial: spin up a full copy of the stack with different parameters. Secrets management is built in, so API keys and database credentials never touch a config file. And when something needs to change, the diff-and-preview workflow (&lt;code&gt;pulumi preview&lt;/code&gt;) catches mistakes before they hit production.&lt;/p&gt;
&lt;h2 id="results"&gt;Results&lt;/h2&gt;
&lt;p&gt;Since launch in September 2025: over 1,700 questions from 83 employees across every team. Usage grew steadily — from around 8 questions per day in the first month to 18 per day by January 2026, with 51 unique users that month alone. This was real production work, not experimentation. Customer analysis for sales calls, resource breakdowns for account managers, blog performance metrics for marketing, policy adoption research for product, ARR deep-dives for leadership.&lt;/p&gt;
&lt;p&gt;The impact on the data team was immediate. Questions that used to land in the #analytics channel and wait for a human now get answered in seconds. The data team shifted from answering routine queries to building better models and improving data quality. We went from being a help desk to being a platform team.&lt;/p&gt;
&lt;p&gt;&lt;img src="pablo-and-lucas.png" alt="Pablo Seibelt and Lucas Crespo, Pulumi&amp;rsquo;s data team"&gt;&lt;/p&gt;
&lt;figcaption&gt;
&lt;center&gt;
&lt;i&gt;
Rumors of our replacement have been greatly exaggerated. Platybot just freed us up for the work that doesn't fit in a Slack reply.
&lt;/i&gt;
&lt;/center&gt;
&lt;/figcaption&gt;
&lt;p&gt;Accuracy is harder to quantify, but the semantic layer gives us confidence. Because Platybot uses pre-defined measures instead of generating arbitrary SQL, entire categories of errors (wrong joins, missing filters, incorrect aggregations) are structurally less likely. When the model does get something wrong, it&amp;rsquo;s usually in interpreting the question, not in the data — and users can verify that through the report&amp;rsquo;s reasoning trace.&lt;/p&gt;
&lt;h2 id="what-we-learned"&gt;What we learned&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The semantic layer matters more than the model.&lt;/strong&gt; We spent more time defining metrics in Cube than we did on any AI work. That investment pays for itself: swap the model, and the answers stay correct. Swap the semantic layer, and nothing works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Meet users where they already are.&lt;/strong&gt; Different UIs handle different cognitive loads. Slack catches the quick questions, the web UI handles deep exploration, and MCP makes data ambient for AI-native workflows. Each interface serves a different mode of thinking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transparency builds trust.&lt;/strong&gt; Showing the AI&amp;rsquo;s reasoning, the queries it ran, and linking to Metabase for verification turned skeptics into regular users. People don&amp;rsquo;t trust a black box, but they&amp;rsquo;ll trust a tool that shows its work.&lt;/p&gt;
&lt;!--
If you're building something similar, here are the resources that helped us:
- [Cube](https://cube.dev/) for building a semantic layer on top of your data warehouse
- [Pulumi IaC getting started](/docs/get-started/) for deploying your infrastructure
- [Pulumi Community Slack](https://slack.pulumi.com) for questions and conversation
&lt;a
href="https://www.pulumi.com/docs/get-started/"
class="btn btn-outline"
&gt;
Try Pulumi for Free
&lt;/a&gt;
--&gt;</description><author>Pablo Seibelt</author><author>Lucas Crespo</author><category>ai</category><category>ai-agents</category><category>data-and-analytics</category><category>internal-tools</category></item><item><title>Speeding up Pulumi Operations by up to 20x</title><link>https://www.pulumi.com/blog/journaling/</link><pubDate>Mon, 12 Jan 2026 18:57:55 +0200</pubDate><guid>https://www.pulumi.com/blog/journaling/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/journaling/index.png" /&gt;
&lt;p&gt;Today we&amp;rsquo;re introducing an improvement that can speed up operations by up to 20x. At every operation, and at every step within an operation, &lt;code&gt;pulumi&lt;/code&gt; saves a snapshot of your cloud infrastructure. This gives &lt;code&gt;pulumi&lt;/code&gt; a current view of state even if something fails mid-operation, but it comes with a performance penalty for large stacks. Here&amp;rsquo;s how we fixed it.&lt;/p&gt;
&lt;h2 id="benchmarks"&gt;Benchmarks&lt;/h2&gt;
&lt;p&gt;Before getting into the more technical details, here are a number of benchmarks demonstrating what this new experience looks like. To run the benchmarks we picked a couple of Pulumi projects: one that can be set up massively parallel, which is the worst case scenario for the old snapshot system, and another that looks a little more like a real world example. Note that we conducted all of these benchmarks in Europe, connecting to Pulumi Cloud, which runs in AWS&amp;rsquo;s &lt;code&gt;us-west-2&lt;/code&gt; region, so exact numbers may vary based on your location and internet connection. This should however give a good indication of the performance improvements.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re benchmarking two somewhat large stacks, both of which are or were used at Pulumi. The first program sets up a website using AWS bucket objects. We&amp;rsquo;re using the &lt;a href="https://github.com/pulumi/examples/tree/master/aws-ts-static-website"&gt;aws-ts-static-website&lt;/a&gt; example here with a small subset of the fraction from our docs site. This means we&amp;rsquo;re setting up more than 3000 bucket objects, with 3222 resources in total.&lt;/p&gt;
&lt;p&gt;The benchmarks were measured using the &lt;code&gt;time&lt;/code&gt; built-in command and using the best time in a best-of-three benchmark. The network traffic was measured using &lt;code&gt;tcpdump&lt;/code&gt;, limiting the measured traffic to only the IP addresses for Pulumi Cloud. Finally, &lt;code&gt;tshark&lt;/code&gt; was used to process the packet captures and count the bytes sent.&lt;/p&gt;
&lt;p&gt;All the benchmarks are run with journaling off (the default experience) and with journaling on (the new experience). To begin with, let&amp;rsquo;s look at the results when creating our stack from scratch:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Bytes sent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without journaling&lt;/td&gt;
&lt;td&gt;58m26s&lt;/td&gt;
&lt;td&gt;16.5MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With journaling&lt;/td&gt;
&lt;td&gt;02m50s&lt;/td&gt;
&lt;td&gt;2.3MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now let&amp;rsquo;s have a look at what this looks like if we only change half the resources, but the remaining ones remain unchanged:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Bytes sent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without journaling&lt;/td&gt;
&lt;td&gt;34m49s&lt;/td&gt;
&lt;td&gt;13.8MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With journaling&lt;/td&gt;
&lt;td&gt;01m45s&lt;/td&gt;
&lt;td&gt;2.3MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The second example is setting up an instance of the Pulumi app and API. Here we&amp;rsquo;ll have an example that&amp;rsquo;s a bit more dominated by the cost of setting up the actual infrastructure in the cloud, but we still have a very noticeable improvement in the time it takes to set up the stack.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Bytes sent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without journaling&lt;/td&gt;
&lt;td&gt;17m52s&lt;/td&gt;
&lt;td&gt;18.5MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With journaling&lt;/td&gt;
&lt;td&gt;9m12s&lt;/td&gt;
&lt;td&gt;5.9MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src="time.png" alt="Comparison chart of the timings shown in the tables above"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="size.png" alt="Comparison chart of the bytes sent shown in the tables above"&gt;&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;To use this feature, you need a &lt;code&gt;pulumi&lt;/code&gt; version newer than v3.211.0, and set the &lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt; environment variable to &lt;code&gt;true&lt;/code&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you are interested in the more technical details read on!&lt;/p&gt;
&lt;h2 id="introduction-to-snapshotting"&gt;Introduction to snapshotting&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pulumi&lt;/code&gt; keeps track of all resources in a stack in a snapshot. This snapshot is stored in the stack&amp;rsquo;s configured backend, which is either the Pulumi Cloud or a DIY backend. Future operations on the stack then use this snapshot to figure out which resources need to be created, updated or deleted.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pulumi&lt;/code&gt; creates a new snapshot at the beginning and at the end of each resource operation to minimize the possibility of untracked changes even if a deployment is aborted unexpectedly (for example due to network issues, power outages, or bugs).&lt;/p&gt;
&lt;p&gt;At the beginning of the operation, &lt;code&gt;pulumi&lt;/code&gt; adds a new &amp;ldquo;pending operation&amp;rdquo; to the snapshot. Pending operations declare the intent to mutate a resource. If a pending operation is left in the snapshot (in other words the operation started, but &lt;code&gt;pulumi&lt;/code&gt; couldn&amp;rsquo;t record the end of it), the next operation will try to resolve this. If we have an ID for the resource already, for example on partial updates/deletes, &lt;code&gt;pulumi&lt;/code&gt; will try to read the resource state from the cloud and resolve it internally. If there is no ID yet, &lt;code&gt;pulumi&lt;/code&gt; will ask the user to check the actual state of the resource. Depending on the user&amp;rsquo;s response, &lt;code&gt;pulumi&lt;/code&gt; will either remove the operation from the snapshot or import the resource. This is because it is possible that the resource has been set up correctly or that the resource creation failed. If &lt;code&gt;pulumi&lt;/code&gt; aborted midway through the operation, it&amp;rsquo;s impossible to know which state the resource is in.&lt;/p&gt;
&lt;p&gt;Once an operation finishes, the pending operation is removed and the resource&amp;rsquo;s final state is recorded in the snapshot.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also some additional metadata that is stored in the snapshot that is only updated infrequently.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how the &lt;a href="https://github.com/pulumi/pulumi/blob/76588836f542c95e8f43ed785cc1828c40369ada/pkg/resource/deploy/snapshot.go#L34"&gt;snapshot looks in code&lt;/a&gt;. This snapshot is serialized and sent to the backend. &lt;code&gt;Resources&lt;/code&gt; holds the list of known resource states and is updated after each operation finishes, and &lt;code&gt;PendingOperations&lt;/code&gt; is the list of pending operations described above.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// a deployment manifest of versions, checksums, and so on.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SecretsManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Manager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the secrets manager to use when serializing this snapshot.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Resources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// all resources and their associated states.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PendingOperations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// all currently pending resource operations.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SnapshotMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// metadata associated with the snapshot.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before we dive in deeper, we also need to understand a little bit about how the &lt;code&gt;pulumi&lt;/code&gt; engine works internally. Whenever a &lt;code&gt;pulumi&lt;/code&gt; operation is run (e.g. &lt;code&gt;pulumi up&lt;/code&gt;, &lt;code&gt;pulumi destroy&lt;/code&gt;, &lt;code&gt;pulumi refresh&lt;/code&gt; etc.), the engine internally generates and executes a series of steps, to create, update, delete etc. resources. To maintain correct relationships between resources, the steps need to be executed in a partial order such that no step is executed until all of the steps it depends on have executed successfully. Steps may otherwise execute concurrently.&lt;/p&gt;
&lt;p&gt;As each step is responsible for updating a single resource, we can generate a snapshot of the state before each step starts, and after it completes. Before each step starts, we create a pending operation, and add it to the &lt;code&gt;PendingOperations&lt;/code&gt; list. After that step completes, we remove the pending operation from that list, and update the &lt;code&gt;Resources&lt;/code&gt; list, either adding a resource, removing it, or updating it, depending on the kind of operation we just executed.&lt;/p&gt;
&lt;p&gt;After this introduction, we can dive into what&amp;rsquo;s slow, how we fixed it, and some benchmarks.&lt;/p&gt;
&lt;h2 id="why-is-it-slow"&gt;Why is it slow?&lt;/h2&gt;
&lt;p&gt;To make sure the state is always as up-to-date as possible, even if there are any network hiccups/power outages etc., a step won&amp;rsquo;t start until the snapshot that includes the pending operation is confirmed to be stored in the backend. Similarly an operation won&amp;rsquo;t be considered finished until the snapshot with an updated resources list is confirmed to be stored in the backend.&lt;/p&gt;
&lt;p&gt;To send the current state to the backend, we simply serialize it as a JSON file, and send it to the backend. However, as mentioned above, steps can be executed in parallel. If we uploaded the snapshot at the beginning and end of every step with no serialization, there would be a risk that we overwrite a new snapshot with an older one, leading to incorrect data.&lt;/p&gt;
&lt;p&gt;Our workaround for that is to serialize the snapshot uploads, uploading one snapshot at a time. This gives us the data integrity properties we want, however it can slow step execution down, especially on internet connections with lower bandwidth, and/or high latency.&lt;/p&gt;
&lt;p&gt;This impacts performance especially for large stacks, as we upload the whole snapshot every time, which can take some time if the snapshot is getting big. For the Pulumi Cloud backend we improved on this a little &lt;a href="https://github.com/pulumi/pulumi/pull/10788"&gt;at the end of 2022&lt;/a&gt;. We implemented a diff based protocol, which is especially helpful for large snapshots, as we only need to send the diff between the old and the new snapshot, and Pulumi Cloud can then reconstruct the full snapshot based on that. This reduces the amount of data that needs to be transferred, thus improving performance.&lt;/p&gt;
&lt;p&gt;However, the snapshotting is still a major bottleneck for large &lt;code&gt;pulumi&lt;/code&gt; operations. Having to serially upload the snapshot twice for each step does still have a big impact on performance, especially if many resources are modified in parallel. Furthermore, the time spent performing textual diffs between snapshots scales in proportion to the size of the data being processed, which adds additional execution time to each operation.&lt;/p&gt;
&lt;h2 id="fast-but-lacking-data-integrity"&gt;Fast, but lacking data integrity?&lt;/h2&gt;
&lt;p&gt;As long as &lt;code&gt;pulumi&lt;/code&gt; can complete its operation, there&amp;rsquo;s no need for the intermediate checkpoints. We could allow &lt;code&gt;pulumi&lt;/code&gt; operations to skip uploading the intermittent checkpoints to the backend. This, of course, avoids the single serialization point we have sending the snapshots to the backend, and thus makes the operation much more performant.&lt;/p&gt;
&lt;p&gt;However, it also has the serious disadvantage of compromising some of the data integrity guarantees &lt;code&gt;pulumi&lt;/code&gt; gives you. If anything goes wrong during the update, &lt;code&gt;pulumi&lt;/code&gt; has no notion of what happened until then, potentially leaving orphaned resources in the provider, or leaving resources in the state that no longer exist.&lt;/p&gt;
&lt;p&gt;Neither of these solutions is very satisfying, as the tradeoff is either performance or data integrity. We would like to have our cake and eat it too, and that&amp;rsquo;s exactly what we&amp;rsquo;re doing with journaling.&lt;/p&gt;
&lt;h2 id="enter-journaling"&gt;Enter journaling&lt;/h2&gt;
&lt;p&gt;To achieve this, we went back to the drawing board, and asked ourselves, &amp;ldquo;What would a solution look like that&amp;rsquo;s both performant &lt;em&gt;and&lt;/em&gt; preserves data integrity throughout the update?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Making that happen is possible because of three facts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We always start with the same snapshot on the backend and the CLI.&lt;/li&gt;
&lt;li&gt;Every step the engine executes affects only one resource.&lt;/li&gt;
&lt;li&gt;We have a service that can reconstruct a snapshot from what is given to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(The third point here already hints at it, but this feature is only available and made possible by Pulumi Cloud, not on the DIY backend).&lt;/p&gt;
&lt;p&gt;What if instead of sending the whole snapshot, or a diff of the snapshot, we could send the individual changes to the base snapshot to the service, which could then apply it, and reconstruct a full snapshot from it? This is exactly what we are doing here, in the form of what we call journal entries. Each journal entry has the following form:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindBegin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindSuccess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindFailure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindRefreshSuccess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindOutputs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindWrite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindSecretsManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindRebuiltBaseState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Version of the journal entry format.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;version&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Kind of journal entry.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;kind&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Sequence ID of the operation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SequenceID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;sequenceID&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID of the operation this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OperationID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;operationID&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID for the delete Operation that this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoveOld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;removeOld&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID for the delete Operation that this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoveNew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;removeNew&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// PendingReplacementOld is the index of the resource that&amp;#39;s to be marked as pending replacement&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PendingReplacementOld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;pendingReplacementOld,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// PendingReplacementNew is the operation ID of the new resource to be marked as pending replacement&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PendingReplacementNew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;pendingReplacementNew,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// DeleteOld is the index of the resource that&amp;#39;s to be marked as deleted.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeleteOld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;deleteOld,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// DeleteNew is the operation ID of the new resource to be marked as deleted.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeleteNew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;deleteNew,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The resource state associated with this journal entry.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ResourceV3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;state,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The operation associated with this journal entry, if any.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;OperationV2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;operation,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If true, this journal entry is part of a refresh operation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RebuildDependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;isRefresh,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The secrets manager associated with this journal entry, if any.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SecretsProvider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;SecretsProvidersV1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;secretsProvider,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// NewSnapshot is the new snapshot that this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NewSnapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DeploymentV3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;newSnapshot,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These journal entries encode all the information needed to reconstruct the snapshot from them. Each journal entry can be sent in parallel from the engine, and the snapshot will still be fully valid. All journal entries have a Sequence ID attached to them, and they need to be replayed in that order on the service side to make sure we get a valid snapshot. It is however okay to replay without journal entries that have not yet been received by the service, and whose sequence ID is thus missing. This is safe because the engine only sends entries in parallel for resources whose parents/dependencies have been fully created and confirmed by the service.&lt;/p&gt;
&lt;p&gt;This way we make sure that the resources list is always in the correct partial order that is required by the engine to function correctly, and for the snapshot to be considered valid.&lt;/p&gt;
&lt;p&gt;The algorithm looks as follows:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Apply snapshot writes. This replaces the full snapshot we have on the service.
# We do this if default providers change, because we don&amp;#39;t emit steps for that, as
# we do for the rest of the operations.
snapshot = find_write_journal_entry_or_use_base(base, journal)
# Track changes
deletes, snapshot_deletes, mark_deleted, mark_pending = set(), set(), set(), set()
operation_id_to_resource_index = {}
# Process journal entries. This is the main algorithm, that adds new resources
# to the snapshot, removes existing ones, deals with refreshes, and operations
# that update outputs.
incomplete_ops = {}
has_refresh = false
index = 0
for entry in journal:
match entry.type:
case BEGIN:
incomplete_ops[entry.op_id] = entry
case SUCCESS:
del incomplete_ops[entry.op_id]
if entry.state and entry.op_id:
resources.append(entry.state)
operation_id_to_resource_index.add(entry.op_id, index)
index++
if entry.remove_old:
snapshot_deletes.add(entry.remove_old)
if entry.remove_new:
deletes[remove_new] = true
if entry.pending_replacement:
mark_pending(entry.pending_replacement)
if entry.delete:
mark_deleted(entry.delete)
has_refresh |= entry.is_refresh
case REFRESH_SUCCESS:
del incomplete_ops[entry.op_id]
has_refresh = true
if entry.remove_old:
if entry.state:
snapshot_replacements[entry.remove_old] = entry.state
else:
snapshot_deletes.add(entry.remove_old)
if entry.remove_new:
if entry.state:
deletes[entry.remove_new] = true
else:
resources.replace(operation_id_to_resource_index(entry.remove_new), entry.state)
case FAILURE:
del incomplete_ops[entry.op_id]
case OUTPUTS:
if entry.state and entry.remove_old:
snapshot_replacements[entry.remove_old] = entry.state
if entry.state and entry.remove_new:
resources.replace(operation_id_to_resource_index(entry.remove_new), entry.state)
deletes = deletes.map(|i| =&amp;gt; operation_id_to_resource_index[i])
# Now that we have marked all the operations, and created a new list of resources, we can
# go through them, and merge the list of new resources and old resources from the snapshot
# that remain together.
for i, res in resources:
if i in deletes:
remove_from_resources(resources, i)
# Merge snapshot resources. These resources have not been touched by the update, and will
# thus be appended to the end of the resource list. We also need to mark existing resources as
# `Delete` and `PendingReplacement` here.
for i, res in enumerate(snapshot.resources):
if i not in snapshot_deletes:
if i in snapshot_replacements:
resources.append(snapshot_replacements[i])
else:
if i in mark_deleted:
res.delete = true
if i in mark_pending:
res.pending_replacement = true
resources.append(res)
# Collect pending operations. These are stored separately from the resources list
# in the snapshot.
pending_ops = [op.operation for op in incomplete_ops.values() if op.operation]
pending_ops.extend([op for op in snapshot.pending_ops if op.type == CREATE])
# Rebuild dependencies if necessary. Refreshes can delete parents or dependencies
# of resources, without affecting the resource itself directly. We need to now remove
# these relationships to make sure the snapshot remains valid.
if has_refresh:
rebuild_dependencies(resources)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The full documentation of the algorithm can be found in our &lt;a href="https://pulumi-developer-docs.readthedocs.io/latest/docs/architecture/deployment-execution/state.html#snapshot-journaling"&gt;developer docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="rollout"&gt;Rollout&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pulumi&lt;/code&gt; state is a very central part of &lt;code&gt;pulumi&lt;/code&gt;, so we wanted to be extra careful with the rollout to make sure we don&amp;rsquo;t break anything. We did this in a few stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We implemented the replay interface inside the &lt;code&gt;pulumi&lt;/code&gt; CLI, and ran it in parallel with the current snapshotting implementation in our tests. The snapshots were then compared automatically, and tests made to fail when the result didn&amp;rsquo;t match.&lt;/li&gt;
&lt;li&gt;Since tests can&amp;rsquo;t cover all possible edge cases, the next step was to run the journaler in parallel with the current snapshotting implementation internally. This was still without sending the results to the service. However we would compare the snapshot, and send an error event to the service if the snapshot didn&amp;rsquo;t match. In our data warehouse we could then inspect any mismatches, and fix them. Since this does involve the service in a minor way, we would only do this if the user is using the Cloud backend.&lt;/li&gt;
&lt;li&gt;Next up was adding a feature flag for the service, so journaling could be turned on selectively for some orgs. At the same time we implemented an opt-in environment variable in the CLI (&lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt;), so the feature could be selectively turned on by users, if both the feature flag is enabled and the user sets the environment variable. This way we could slowly start enabling this in our repos, e.g. first in the integration tests for &lt;code&gt;pulumi/pulumi&lt;/code&gt;, then in the tests for &lt;code&gt;pulumi/examples&lt;/code&gt; and &lt;code&gt;pulumi/templates&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Allow users to start opting in. If you want to opt-in with your org, please reach out to us, either on the &lt;a href="https://slack.pulumi.com/"&gt;Community Slack&lt;/a&gt;, or through our &lt;a href="https://support.pulumi.com/hc/en-us"&gt;Support channels&lt;/a&gt;, and we&amp;rsquo;ll opt your org into the feature flag. Then you can begin seeing the performance improvements by setting the &lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt; env variable to true.&lt;/li&gt;
&lt;li&gt;Turn on the feature flag for everyone, but still require the &lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt; env variable to be set to true. (We are here right now).&lt;/li&gt;
&lt;li&gt;Flip the feature on by default, but still allow users to opt out using a &lt;code&gt;PULUMI_DISABLE_JOURNALING&lt;/code&gt; env variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;While these performance improvements hopefully make your day to day use of &lt;code&gt;pulumi&lt;/code&gt; quicker and more enjoyable, we&amp;rsquo;re not quite done here. We&amp;rsquo;re looking at some other performance improvements, that will hopefully speed up your workflows even more.&lt;/p&gt;</description><author>Thomas Gummerer</author><category>journaling</category><category>performance</category><category>data-integrity</category><category>features</category></item><item><title>A Recipe for a Better AI-based Code Generator</title><link>https://www.pulumi.com/blog/codegen-learnings/</link><pubDate>Tue, 07 Jan 2025 20:00:00 -0500</pubDate><guid>https://www.pulumi.com/blog/codegen-learnings/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/codegen-learnings/index.png" /&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Note: This post discusses Pulumi Copilot, which Pulumi Neo has replaced. &lt;a href="https://www.pulumi.com/docs/ai/"&gt;Learn about Neo →&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When asked about his research process, Anthony Bourdain would describe how he&amp;rsquo;d blend his formal culinary training with deep dives into local food culture - from market stalls to family recipes. Modern AI code generation follows a similar path: it can&amp;rsquo;t just rely on what it knows - it must tap into continuously evolving, domain-specific knowledge bases. Just as Bourdain would combine his classical French training with techniques learned from local kitchens, AI code generators blend their built-in knowledge with retrieved code snippets and type definitions to generate code that accurately represents the user&amp;rsquo;s intent.&lt;/p&gt;
&lt;p&gt;This fusion of base knowledge with contextual understanding is especially valuable for Infrastructure as Code (IaC), where rapidly evolving cloud providers and libraries make manual development challenging, traditional debugging cycles impractical, and errors catastrophically expensive.&lt;/p&gt;
&lt;p&gt;The role of IaC won&amp;rsquo;t diminish in the age of AI - if anything, it will become even more central as systems grow increasingly complex and automated. Trustworthy code generators will be a key ingredient in the recipe for modern infrastructure management.&lt;/p&gt;
&lt;p&gt;In this post, we share how we developed code generation for Pulumi and what we learned, based on both our production IaC generator powering &lt;a href="https://www.pulumi.com/solutions/ai/"&gt;Pulumi AI&lt;/a&gt; and &lt;a href="https://www.pulumi.com/product/copilot/"&gt;Pulumi Copilot&lt;/a&gt;, as well as features and approaches we&amp;rsquo;re still exploring.&lt;/p&gt;
&lt;h2 id="using-rag-for-code-generation"&gt;Using RAG for code generation&lt;/h2&gt;
&lt;p&gt;Pulumi supports over &lt;a href="https://www.pulumi.com/registry/"&gt;120 providers&lt;/a&gt;, including major cloud providers such as AWS, Azure, Google Cloud, and Kubernetes, as well as many other services and platforms. New providers are continuously added, and existing providers change as their capabilities grow.&lt;/p&gt;
&lt;p&gt;Our goal is to generate the most accurate code for every provider - code that is not only correct but also reflects their latest capabilities.&lt;/p&gt;
&lt;p&gt;LLMs are great at generating code however they are limited to what they have learned before their knowledge cutoff date. This means that the latest changes in the providers will not be reflected in the generated code. This also could lead to hallucinations when the model struggles to answer a question that requires up to date knowledge.&lt;/p&gt;
&lt;p&gt;To help us solve this, we rely on the technique known as the &lt;em&gt;Retrieval Augmented Generation&lt;/em&gt; (RAG). RAG helps code generation by integrating information retrieved from external data sources. In Pulumi, we call this data source the &lt;em&gt;Registry&lt;/em&gt; - it&amp;rsquo;s the database we maintain that contains type schema and usage information for every provider.&lt;/p&gt;
&lt;p&gt;At a high level, using RAG involves the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Analyze user&amp;rsquo;s question.&lt;/li&gt;
&lt;li&gt;Look up the pertinent information from the Registry.&lt;/li&gt;
&lt;li&gt;Format that information in a system prompt that LLM can understand.&lt;/li&gt;
&lt;li&gt;Make the LLM call asking it to generate the code using the additional information in the prompt.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is lot of fascinating details here, so let&amp;rsquo;s dig in!&lt;/p&gt;
&lt;h2 id="the-anatomy-of-pulumi-copilot-rag"&gt;The anatomy of Pulumi Copilot RAG&lt;/h2&gt;
&lt;p&gt;Before going into the details, let’s consider a simple yet essential insight: an effective dataset for RAG must meet two key requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It must &lt;em&gt;contain&lt;/em&gt; the necessary information.&lt;/li&gt;
&lt;li&gt;The information must be easily &lt;em&gt;searchable&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s take a sample user query:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Generate code for S3 Bucket&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To fulfil the request, the system needs to intuit the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;cloud provider&lt;/strong&gt;. This can be determined based on the fact that S3 is a common storage solution in AWS, and possibly the fact that the user&amp;rsquo;s &lt;a href="https://www.pulumi.com/blog/copilot-system-prompts/"&gt;organization has stated&lt;/a&gt; AWS as their preferred cloud provider.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;programming language&lt;/strong&gt;. This information can again come directly from the organizational preferences, or from the user&amp;rsquo;s prior conversations.&lt;/li&gt;
&lt;li&gt;The information about the &lt;strong&gt;type&lt;/strong&gt; (or types) that must be created - its name and schema, the package it is in, and the capabilities it supports. Some of this information comes from the built-in LLM knowledge, supported and augmented by the Registry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Putting it all together, we can now expand the original user query into the following prompt that is going to guide the code generation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Generate TypeScript code for S3 Bucket, the AWS resource defined in package &lt;code&gt;@pulumi/aws&lt;/code&gt;, type &lt;code&gt;aws.s3.Bucket&lt;/code&gt;
with its schema defined as follows: &amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While we had to rely on some guesswork to come up with this prompt, fortunately this process can be iterative - if we don&amp;rsquo;t get all of it right the first time, we can try again with additional information that will help us refine the results. This is an important point we return to later in the post. &lt;!-- ref to self-debugging --&gt;&lt;/p&gt;
&lt;h3 id="assessing-search-quality-using-recall-and-precision"&gt;Assessing search quality using recall and precision&lt;/h3&gt;
&lt;p&gt;To assess how effective the RAG is, we need to first understand the two fundamental concepts used in the information retrieval systems: the &lt;em&gt;recall&lt;/em&gt; and the &lt;em&gt;precision&lt;/em&gt;. Imagine that you&amp;rsquo;re looking for apple pie recipes in one of Jamie Oliver&amp;rsquo;s cookbooks. The book has a recipe for a classic American apple pie, a Dutch apple pie and a modern take on a French apple tart. Due to the book&amp;rsquo;s narrative approach with the recipes woven into the stories and context, you&amp;rsquo;ve managed to retrieve only the first two recipes but missed the French apple tart. Having retrieved 2 ouf 3 relevant documents, you have achieved a &lt;strong&gt;67% recall&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Because you were looking for the word &amp;ldquo;pie&amp;rdquo;, you also retrieved a recipe for a Shepherd&amp;rsquo;s pie, which, while delicious, does not qualify as an apple pie. Another document that came up was a fish pie - a classic British dish that does not contain apples or even a pastry crust. Since only 2 of your 4 retrieved documents can be legitimately classified as apple pies, you have achieved a &lt;strong&gt;50% precision&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s formalize this a bit. Recall measures the ratio of the relevant documents retrieved to the total number of relevant documents in RAG dataset:&lt;/p&gt;
&lt;p&gt;$$Recall = \frac{N(Retrieved \cap Relevant)}{N(Relevant)}$$&lt;/p&gt;
&lt;p&gt;Where&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$N(Retrieved \cap Relevant)$ is the number of documents that are both retrieved and relevant.&lt;/li&gt;
&lt;li&gt;$N(Relevant)$ is the total number of relevant documents in the database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Good recall means that many documents relevant to the query were retrieved.&lt;/p&gt;
&lt;p&gt;Precision is the ratio of the relevant documents retrieved to the total number of retrieved documents:&lt;/p&gt;
&lt;p&gt;$$Precision = \frac{N(Retrieved \cap Relevant)}{N(Retrieved)}$$&lt;/p&gt;
&lt;p&gt;Where $N(Retrieved)$ is the total number of documents that were retrieved.&lt;/p&gt;
&lt;p&gt;High precision means that many of the retrieved documents were relevant.&lt;/p&gt;
&lt;p&gt;Naturally, we want to maximize both the recall and the precision. It&amp;rsquo;s &lt;a href="https://buduroiu.com/blog/rag-llm-recall-problem"&gt;been said&lt;/a&gt; that high recall is essential to ensure relevant content is available to the code generator while precision is the parameter you want to optimize for to avoid hallucinations.&lt;/p&gt;
&lt;h3 id="practical-concerns"&gt;Practical concerns&lt;/h3&gt;
&lt;p&gt;Precision and recall are essential in understanding the information retrieval quality, but they are quite hard to measure in practice. Unlike a cookbook, Pulumi registry contains thousands of ever changing documents, and evaluating how many of them are relevant for every user-submitted query is impractical. This makes recall evaluation for live traffic next to impossible. Things are a little easier with precision, where we&amp;rsquo;re dealing with a small number of documents, but even that metric requires a non-trivial evaluation of relevance, which needs an LLM call or a human judge.&lt;/p&gt;
&lt;p&gt;Fortunately, other metrics that often can effectively estimate retrieval quality have been developed. We have found a metric that can predict, with some degree of accuracy, whether the generated code will successfully compile. For this metric, we compare the &lt;em&gt;tokens&lt;/em&gt; present in the LLM-produced prompt with the number of tokens present in the generated code. (By token here we understand a compiler token - an identifier such as the name of a class, method or a field and not a traditional LLM token concept),
Intuitively, if a token present in the prompt also appears in the generated program, it can be assumed that the token contributed to the generated program. Tokens in the generated program that were not part of the prompt are not necessarily wrong but they are less trusted (they can come from the LLM built-in knowledge or were guessed)&lt;/p&gt;
&lt;p&gt;$$prompt \ coverage = \frac{N(\text{Tokens in prompt} \cap \text{Tokens in code})}{N(\text{Tokens in code})}$$&lt;/p&gt;
&lt;!-- Note: our documents call is Recall, which is not how industry uses this term (see above) --&gt;
&lt;p&gt;Prompt coverage is a metric we can observe in production, and it&amp;rsquo;s one of several metrics we use when updating providers to ensure we haven&amp;rsquo;t regressed the quality of the RAG.&lt;/p&gt;
&lt;h3 id="semantic-search-with-vector-embeddings"&gt;Semantic search with vector embeddings&lt;/h3&gt;
&lt;p&gt;Semantic search is based on the conceptual similarity of the term you&amp;rsquo;re looking for with the elements in the data store. For example, searching for &amp;ldquo;dumplings&amp;rdquo; can return terms like pierogi and gyoza - words with different spelling but both representing different types of filled dough preparations.&lt;/p&gt;
&lt;p&gt;A common way to determine the similarity between the two strings is to first turn these strings into &lt;em&gt;vector embeddings&lt;/em&gt; - arrays of floating point values representing the semantic meaning of each string - and then calculate the &lt;em&gt;cosine similarity&lt;/em&gt; between the two vectors, which is the cosine of the angle between the vectors. &lt;a href="https://huggingface.co/blog/matryoshka"&gt;Various methods&lt;/a&gt; of producing vector embeddings are fascinating but we will not cover them here in depth.&lt;/p&gt;
&lt;p&gt;For Pulumi code generation we are using the OpenAI&amp;rsquo;s &lt;a href="https://www.downelink.com/a-deep-dive-into-openais-text-embedding-ada-002-unlocking-the-power-of-semantic-understanding/"&gt;Ada-002 embedding model&lt;/a&gt; which at this moment represents a good balance between performance and cost.&lt;/p&gt;
&lt;p&gt;Producing vector embeddings from the user query is the standard approach in this situation. However, for Pulumi code generator we added a little twist - to increase the odds of getting more relevant information from the Registry (i.e. to increase the recall) we first make an LLM call to generate a small set of relevant search terms that will produce an array of vector embeddings.&lt;/p&gt;
&lt;div style="text-align: center; width: 100%; margin: 0 auto;"&gt;
&lt;img src="flow-embeddings.png" alt="" style="width: 100%;"&gt;
&lt;figcaption&gt;
&lt;i&gt;Getting vector embeddings from user query&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;p&gt;We then use the array of vector embeddings to retrieve the set of relevant documents from the Registry.&lt;/p&gt;
&lt;h3 id="full-text-vs-semantic-search"&gt;Full text vs semantic search&lt;/h3&gt;
&lt;p&gt;While semantic search is essential for any modern information retrieval system, we should not forget simple and effective methods for text search already exist. The &amp;ldquo;S3 Bucket&amp;rdquo; part of the user search happens to be easily searchable using traditional text search operations (such as SQL &lt;code&gt;LIKE&lt;/code&gt; operator).&lt;/p&gt;
&lt;p&gt;To be effective, our RAG must be able to handle queries that require semantic understanding (such as &amp;ldquo;simple storage service in AWS&amp;rdquo;) as well as the traditional text search to support situations where the user knows exactly what they are looking for. To that end, the industry has adopted an approach known as the &amp;ldquo;hybrid search&amp;rdquo;, in which the results of full-text search and semantic search are combined to provide the final result.&lt;/p&gt;
&lt;p&gt;For each of these search terms we generate a query that combines the full text search with the semantic search. The resulting documents are then evaluated based on the following criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Dense score&lt;/em&gt;: Vector similarity using cosine distance between query embedding and stored embeddings&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sparse score&lt;/em&gt;: Text search relevance according to the full-using algorithm (PostgreSQL full-text search in our case)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both the dense and the sparse scores contribute to the final score that is used to sort the documents. Even though the final score is the average of the two in our current implementation, it would not be correct to say that they have the same weight, or the influence on the end result. This is so because the normalization of the scores must take into account their distribution in the real-world queries and calculating that is quite complex.&lt;/p&gt;
&lt;p&gt;Boosting the influence of one score relative to the other is an area we&amp;rsquo;re actively exploring, and achieving the optimal result depends highly on what parameters we decide to optimize for - high quality code, time to result and so on.&lt;/p&gt;
&lt;p&gt;Finally, we apply the rank-based scoring technique in which the results are penalized based on their position in the ranking. This creates separation between results that might have similar initial scores and gives preference to results that appear earlier in the ranking.&lt;/p&gt;
&lt;h3 id="pruning-the-results"&gt;Pruning the results&lt;/h3&gt;
&lt;p&gt;Our Pulumi code generator employs a two-phase document selection strategy. The first phase casts a wide net, gathering all potentially relevant documentation to maximize recall. The second phase applies precision-focused filtering to distill this collection down to the most pertinent documents.&lt;/p&gt;
&lt;p&gt;This filtering step serves two purposes. First, it prevents LLM hallucinations that arise from similarly-named types across different providers. Second, it optimizes performance by keeping prompts concise - a critical consideration given that larger prompts increase both latency and computational costs, even when within context window constraints.&lt;/p&gt;
&lt;p&gt;Through empirical testing with the Pulumi Registry search, we&amp;rsquo;ve established these baseline parameters: a maximum of 10 documents per query term selected by relevance score, and a 20K token ceiling for prompts. While these parameters have yielded good results in practice, they are likely not optimal for all scenarios. We continue to iterate on these values through ongoing experimentation.&lt;/p&gt;
&lt;h3 id="prompt-generation"&gt;Prompt generation&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re now ready to create the system prompt for the code generation LLM call! We&amp;rsquo;ve already discussed some of the elements needed to build the effective prompt - the original user query, the search terms and the vector embeddings that produce the set of documents that will guide the code generation process.&lt;/p&gt;
&lt;p&gt;There is another element that goes into the prompt - a concise set of instructions produced by an LLM call based on the original user&amp;rsquo;s query. The approach when the output of one prompt is used as input for another is known as &amp;ldquo;prompt chaining&amp;rdquo;, and we used it to provide step-by-step instructions to the code generator. For our query, this set of instructions can look as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Create an S3 bucket using Pulumi in TypeScript using the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Import the necessary Pulumi AWS package.&lt;/li&gt;
&lt;li&gt;Define a new S3 bucket resource with basic configuration.&lt;/li&gt;
&lt;li&gt;Export the bucket name as an output.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;div style="text-align: center; width: 100%; margin: 0 auto;"&gt;
&lt;img src="retrieval.png" alt="" style="width: 100%;"&gt;
&lt;figcaption&gt;
&lt;i&gt;Composition of the system prompt for code generation&lt;/i&gt;
&lt;/figcaption&gt;
&lt;/div&gt;
&lt;p&gt;Finally, we use the resulting prompt to call the LLM and ask it to generate the code. We&amp;rsquo;re done!&lt;/p&gt;
&lt;h3 id="self-debugging"&gt;Self-debugging&lt;/h3&gt;
&lt;p&gt;Unfortunately, this isn&amp;rsquo;t always the final step of the process. Despite our best efforts, the code produced by the LLM will not always be correct.&lt;/p&gt;
&lt;p&gt;At this point, it&amp;rsquo;s worth pondering what &amp;ldquo;correct code&amp;rdquo; means. The generated program might have the following problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It might be syntactically or semantically incorrect, i.e. it might not compile or fail to typecheck.&lt;/li&gt;
&lt;li&gt;It might fail at runtime - for example by referring to a non-existing resource, a region and so on.&lt;/li&gt;
&lt;li&gt;It might run &amp;ldquo;successfully&amp;rdquo; but not do what the user intended - either because the user did not express themselves clearly or because the request was misunderstood.&lt;/li&gt;
&lt;li&gt;Finally, the code might run and do exactly what the user wants, but lead to an undesired outcome, for example loss of an asset, or a security vulnerability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Solutions to many of these problems go well beyond the domain of code generation.&lt;/p&gt;
&lt;p&gt;However, the first of these problems can be addressed by the approach known as &amp;ldquo;self-debugging&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;We observed that many generated TypeScript programs that fail to typecheck contain only a few errors, and asking the LLM to fix these errors often produces a valid program. While this approach is still experimental, we see promising results where getting to a correct program requires only 1-2 iterations of self-debugging. The biggest challenge with this approach is doing it in real-time. The user is staring at the screen waiting for an answer, so latency is a major concern.&lt;/p&gt;
&lt;p&gt;Monitoring these typechecking errors in production can also provide valuable insight into the quality of the data used by the RAG and even suggest specific solutions. For example, failure to typecheck a member-access expression is a likely indicator of a missing type schema (a recall problem) or a &amp;ldquo;wrong&amp;rdquo; schema brought in by an irrelevant document (a precision problem).&lt;/p&gt;
&lt;p&gt;Self-debugging can also be extended to include the &lt;code&gt;pulumi preview&lt;/code&gt; command, which is a &amp;ldquo;dry run&amp;rdquo; operation before the actual deployment and can detect many real or potential problems such as destructive actions, incorrect configurations that cannot be detected at compile time, dependency conflicts, and policy violations.&lt;/p&gt;
&lt;h2 id="from-kitchen-to-table-testing-what-works"&gt;From kitchen to table: testing what works&lt;/h2&gt;
&lt;p&gt;The landscape of LLM-based code generation is moving fast, and we need to keep learning and adapting as we go. But with all this rapid technological change, it&amp;rsquo;s crucial to ground our decisions in real numbers. We need to make sure each new advancement actually makes things better, both in our test environments and out in the real world.&lt;/p&gt;
&lt;p&gt;The probabilistic nature of LLM-based code generation means we can&amp;rsquo;t rely solely on pre-production testing. Instead, we adopt multiple layers of quality control working together. Here&amp;rsquo;s what we&amp;rsquo;ve learned works best:&lt;/p&gt;
&lt;h3 id="building-confidence-through-testing"&gt;Building confidence through testing&lt;/h3&gt;
&lt;p&gt;Our testing strategy combines several approaches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Systematic experimentation&lt;/strong&gt;: We run &amp;ldquo;what-if&amp;rdquo; scenarios against various test datasets to understand how the system behaves under different conditions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local evaluation pipeline&lt;/strong&gt;: While local testing has its limitations, it helps catch obvious issues early in development. We evaluate the performance of the code generator and run the code through the typechecker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deterministic generation&lt;/strong&gt;: We set the LLM temperature to 0 to ensure consistent outputs. For code generation, consistency and repeatability matters more than creative variations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="monitoring-quality-in-production"&gt;Monitoring quality in production&lt;/h3&gt;
&lt;p&gt;We track several key metrics to ensure the system performs well:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RAG effectiveness&lt;/strong&gt;: We measure prompt coverage to evaluate how well our retrieval system performs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Code quality indicators&lt;/strong&gt;: We track the percentage of generated code that successfully typechecks for all supported languages along with how this metric responds to system changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User feedback&lt;/strong&gt;: Every &amp;ldquo;thumbs down&amp;rdquo; report gets analyzed to identify patterns and potential improvements. This direct user feedback has been invaluable in refining our system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Building an effective AI-powered code generator requires carefully balancing multiple concerns: the raw capabilities of LLMs with retrieved contextual knowledge, semantic search with traditional text matching, and thorough testing with real-world scenarios.&lt;/p&gt;
&lt;p&gt;Our experience has taught us that success lies in treating code generation like a delicate soufflé - it requires precise measurements, careful monitoring, and constant refinement of technique. The key ingredients are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A robust RAG system with well-tuned recall and precision.&lt;/li&gt;
&lt;li&gt;End-to-end testing and monitoring across development and production.&lt;/li&gt;
&lt;li&gt;Self-debugging capabilities to handle common issues like type checking errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As we continue to evolve Pulumi&amp;rsquo;s code generation capabilities, we&amp;rsquo;re excited about expanding our self-debugging features and further refining our RAG implementation.&lt;/p&gt;
&lt;p&gt;We invite you to try these capabilities in your own projects and share your experiences. Your feedback helps us continue improving and advancing the state of AI-assisted infrastructure development.&lt;/p&gt;
&lt;!--raw material
1.2. get multiple "Pulumi Registry schema" elements (40 in our case, 29 unique) - some of them less relevant.
(We call them tokens internally but they are really type names)
This search uses vector embeddings
Resource metadata:
{
"name": "aws-native",
"version": "0.90.0",
"token": "aws-native:s3:Bucket",
"kind": "resource"
}
AWS native looks like this:
```
{
name: "aws-native",
version: "0.90.0",
token: "aws-native:s3:Bucket",
dense_score: 0.8639781475067139,
sparse_score: 0,
score: 0.3887901663780213,
dense_score_boosted: 0.7775803327560425,
sparse_score_boosted: 0,
kind: "resource",
text: "Create an S3 Bucket on AWS Native (preview)",
definition: {
...
```
They include the Definition structure that defines the JSON schema type for every such token.
Less relevant providers like yandex:
```
{
name: "yandex",
version: "0.13.0",
token: "yandex:index/storageBucket:StorageBucket",
...
```
They are then sorted by their density score.
Resulting generated prompt can be 1K or more lines of Yaml
4. Full text search and BM25
BM25:
- Inverse Document Frequency: how rare is the query term
- Term frequency in the document: how often does the term appear in the document
Detailed explanation: https://emschwartz.me/understanding-the-bm25-full-text-search-algorithm/
5. Vector embeddings, combine the two
similarity metrics like cosine similarity.
6. Reranking
6.1. It's good to have good recall - you can throw everything and the kitchen sink at the LLM - but too much information can actually be counterproductive:
- Context window limitation: LLMs have limits on how much text they can process, known as the “context window.” Even though modern LLMs support bigger context window, there is always a limit.
- Accuracy: recall relevant information.
- Cost: we pay by the token.
We need to pare if down.
Reranker analyzes the documents and assigns it a relevance score. It may consider additional features such as organizational preferences or user's prior history that can make the document more relevant.
## Evaluate quality of code generation
1. Does RAG even help? For many common providers, LLMs can already produce reasonably good code. How do we know that RAG adds value?
Ultimately, the only measure of quality that matters is whether the generated code correctly represents the user's intent. However, this is hard to test in an automated way.
One measure is recall: &lt;TODO: define&gt;
What about precision?
2. Evaluate generated programs
How do we assess the quality of our RAG? Intuitively, we want two things to be true:
- Useful information must be in the database
- We must have effective ways of finding that information
- Recall
- Typecheck
- `pulumi up` - a "dry run" before the actual deployment and can detect many real or potential problems such potentially destructive actions, incorrect configurations that cannot be detected at compile time, dependency conflicts and policy violations.
--&gt;</description><author>Artur Laksberg</author><author>Simon Howe</author><category>copilot</category><category>ai</category><category>infrastructure-as-code</category></item><item><title>AI Engineering Lessons from Building Pulumi Copilot</title><link>https://www.pulumi.com/blog/copilot-lessons/</link><pubDate>Thu, 12 Dec 2024 13:56:13 -0500</pubDate><guid>https://www.pulumi.com/blog/copilot-lessons/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/copilot-lessons/index.png" /&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Note: This post discusses Pulumi Copilot, which Pulumi Neo has replaced. &lt;a href="https://www.pulumi.com/docs/ai/"&gt;Learn about Neo →&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Building AI-powered developer tools comes with unique challenges, and now that we&amp;rsquo;ve &lt;strong&gt;&lt;a href="https://www.pulumi.com/blog/pulumi-copilot-rest/"&gt;launched our REST API&lt;/a&gt;&lt;/strong&gt;, we want to share some lessons we&amp;rsquo;ve learned building Pulumi Copilot, an AI assistant for cloud infrastructure.&lt;/p&gt;
&lt;p&gt;One of the big challenges was determining what &amp;lsquo;working&amp;rsquo; really meant. So when a message landed in our feedback channel after months of rigorous testing - &amp;lsquo;Your tool doesn&amp;rsquo;t know anything!&amp;rsquo; - it caused some mild panic. We&amp;rsquo;d just made some changes, so we braced for the worst. But our evals were still looking strong, so what was going on?&lt;/p&gt;
&lt;p&gt;The user was attempting to force-delete a stack that still contained resources. But when we dug deeper, we found something fascinating: Copilot had confidently suggested a &amp;lsquo;&amp;ndash;force&amp;rsquo; flag, which would have been a logical solution&amp;hellip; except this flag doesn&amp;rsquo;t exist in Pulumi. Our AI was hallucinating exactly what the user wanted. But this wasn&amp;rsquo;t just a bug - it was the first of many insights that would reshape how we approach AI-powered tools.&lt;/p&gt;
&lt;p&gt;To understand how we got here - and why this &amp;ldquo;error&amp;rdquo; actually taught us something valuable about our product - let&amp;rsquo;s start with the core challenge we faced: balancing traditional software engineering with this new world of prompt engineering.&lt;/p&gt;
&lt;h2 id="engineering-for-reality-prompt-engineering-vs-software-engineering"&gt;Engineering for Reality: Prompt Engineering vs Software Engineering&lt;/h2&gt;
&lt;p&gt;&lt;img src="soft-eng.png" alt="Software Engineering vs Prompt Engineering"&gt;&lt;/p&gt;
&lt;p&gt;When building LLM-powered applications, it&amp;rsquo;s tempting to throw every task at the model. Modern LLMs can generate code, format text, and create clickable links. But this approach carries hidden costs.&lt;/p&gt;
&lt;p&gt;Working on Copilot taught us a key lesson: let the LLM do what it does best and use good old imperative code for everything else.&lt;/p&gt;
&lt;p&gt;Take a seemingly simple feature: listing a user&amp;rsquo;s Pulumi stacks with clickable links based on data from a backend API. Our first implementation used a complex prompt instructing the LLM to construct URLs in the format &lt;code&gt;app.pulumi.com/org/project/stack&lt;/code&gt;. The prompt explained the format, provided examples, and asked the LLM to generate these links from JSON data it had.&lt;/p&gt;
&lt;p&gt;It worked - almost all of the time. But we were seeing occasional malformed URLs and more importantly, this was burning input tokens (and money) on a complicated prompt that made AI construct strings that could be deterministically generated.&lt;/p&gt;
&lt;p&gt;The solution was straightforward: generate the full links in the backend service and include them directly in the context. The LLM then needs no instructions on how to create them. Simple stuff, but it gave faster responses and perfect URLs at a lower cost.&lt;/p&gt;
&lt;p&gt;When you find yourself writing elaborate prompts to handle structured data transformations, stop and ask: Could traditional code do this better? Could this be decomposed so that the LLM does less?&lt;/p&gt;
&lt;p&gt;To validate this approach, we tested Copilot ourselves to see what worked.&lt;/p&gt;
&lt;h2 id="copilot-in-action-real-world-dogfooding"&gt;Copilot in Action: Real-World Dogfooding&lt;/h2&gt;
&lt;p&gt;The internal testing phase taught us invaluable lessons about how people would actually use the tool. We watched our team try Copilot in their daily work, and three common use cases emerged:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging Deployments:&lt;/strong&gt; LLMs clearly excel at summarization. One of the first questions our internal users asked was, &amp;lsquo;Why did my latest infrastructure deployment fail?&amp;rsquo; Using Copilot to extract a clear natural language explanation requests like these has been a clear win.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding Complex Infrastructure:&lt;/strong&gt; Copilot helped our engineers gain insights into Pulumi&amp;rsquo;s own infrastructure. Asking, &amp;lsquo;How many resources are in production?&amp;rsquo; &amp;lsquo;What expensive compute is running&amp;rsquo; or &amp;lsquo;What version are the EKS clusters in EU?&amp;quot; shows the value of allowing users to express infrastructure questions in natural language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generating Code:&lt;/strong&gt; One of the first queries logged was, &amp;lsquo;I want a static website on AWS behind a CloudFront CDN.&amp;rsquo; Another came from a Solutions Engineer, tasked with demonstrating Pulumi&amp;rsquo;s CrossGuard policy engine to a prospect, asking Copilot to generate policy code.&lt;/p&gt;
&lt;p&gt;These early experiences showed the value of Copilot. But they also revealed the need for a systematic approach to handling diverse user queries. This led to the development of what we call skills.&lt;/p&gt;
&lt;h2 id="skillful-slicing-modular-mastery"&gt;Skillful Slicing: Modular Mastery&lt;/h2&gt;
&lt;p&gt;As Copilot grew, we broke it into smaller pieces we call skills. Each skill does one specific job. The Insights skill handles queries about resource usage and configuration (&amp;ldquo;How many S3 buckets do I have?&amp;rdquo;), the Cloud Skill interacts with the Pulumi Service API to manage infrastructure (&amp;ldquo;Show me my stacks.&amp;rdquo;), the Code Skill generates Pulumi code snippets (&amp;ldquo;Write a Typescript program&amp;hellip;&amp;rdquo;), and the Docs Skill retrieves information from Pulumi documentation (&amp;ldquo;How do I use &lt;a href="https://www.pulumi.com/docs/iac/concepts/update-plans/"&gt;update plans&lt;/a&gt;?&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;When you ask Copilot something, it figures out what you need and picks the right skill for the job – like a manager deciding which expert to send your question to. This &lt;a href="https://platform.openai.com/docs/guides/function-calling"&gt;function-calling&lt;/a&gt; approach, orchestrated by a component we call the &amp;ldquo;outer loop,&amp;rdquo; allows Copilot to access and process information beyond its internal knowledge base.&lt;/p&gt;
&lt;p&gt;Fortunately, Pulumi Cloud already exposes a &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/reference/cloud-rest-api/"&gt;rich API&lt;/a&gt; - in fact, this is what powers the Pulumi Console and the Pulumi CLI - so all we had to do is to build a &lt;em&gt;skill&lt;/em&gt; that maps the user query to the appropriate Pulumi Cloud REST API. A question like &amp;ldquo;Show me my stacks&amp;rdquo; translates into the &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/reference/cloud-rest-api/#list-stacks"&gt;List Stacks&lt;/a&gt; API call. A question like &amp;lsquo;Show me my untagged EC2 instances&amp;rsquo; is a bit more complex but it breaks down into clear components - resource type (EC2), filter condition (untagged) - that route to the Insights skill. This mapping helped us handle the many ways users phrase the same technical request.&lt;/p&gt;
&lt;p&gt;Refining this routing system revealed another opportunity: streamlining the Debug button workflow.&lt;/p&gt;
&lt;h2 id="debug-dispatch"&gt;Debug Dispatch&lt;/h2&gt;
&lt;p&gt;&lt;img src="optimize.png" alt="Before and After"&gt;&lt;/p&gt;
&lt;p&gt;Originally, when a user clicked &amp;lsquo;Debug with Copilot&amp;rsquo;, the system would send a text query like &amp;ldquo;Analyze this update and explain any errors.&amp;rdquo; Copilot would then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Determines the user wants to analyze an update&lt;/li&gt;
&lt;li&gt;Identifies which API to call&lt;/li&gt;
&lt;li&gt;Calls the API&lt;/li&gt;
&lt;li&gt;Summarize the result&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Having the LLM figure out what the user wants is the right approach in general, but in this case, we already know the user&amp;rsquo;s intent - they clicked a debug button. So, we can directly call the analysis API to get the results and use the LLM solely for what it does best: summarizing technical output into clear, actionable explanations.&lt;/p&gt;
&lt;p&gt;This is another small win for the &amp;ldquo;Software Engineering over Prompt Engineering&amp;rdquo; approach. Traditional code handles the predictable parts, while AI focuses on the human-facing explanations.&lt;/p&gt;
&lt;p&gt;But while minimizing the LLM workload helped with efficiency, we soon faced an even trickier challenge: the deceptive polish of AI-generated outputs.&lt;/p&gt;
&lt;h2 id="the-illusion-of-correctness"&gt;The Illusion of Correctness&lt;/h2&gt;
&lt;p&gt;&lt;img src="false-info.png" alt="Before and After"&gt;&lt;/p&gt;
&lt;p&gt;Large language models excel at generating well-structured, grammatically correct output. They make neat tables, tell good stories, and generally sound confident. That&amp;rsquo;s what makes them dangerous because this polished presentation can mask underlying flaws in the information itself, creating a false sense of confidence for users.&lt;/p&gt;
&lt;p&gt;One of our early testers, Pablo, a data engineer at Pulumi, encountered this firsthand. He posed a query to Pulumi Copilot, asking for a summary of resources within a specific project. The response he received was impeccably formatted, neatly categorizing resources by type and providing counts for each. It &lt;em&gt;looked&lt;/em&gt; right, and for us humans sometimes looking right carries a lot of weight.&lt;/p&gt;
&lt;p&gt;However, a closer inspection revealed the numbers were way off. Copilot had asked for the wrong data and then summarized it beautifully - but incorrectly. This highlighted our next challenge: how do you systematically test a system that can be confidently wrong while sounding completely right?&lt;/p&gt;
&lt;h2 id="testing-the-untestable-validating-llm-outputs"&gt;Testing the Untestable: Validating LLM Outputs&lt;/h2&gt;
&lt;p&gt;When testing traditional code, we expect consistent, predictable outputs. With LLMs, even correct answers can vary. Here&amp;rsquo;s how we tackle this challenge.&lt;/p&gt;
&lt;p&gt;Our first approach was simple: keyword checks. For example, when testing the update analysis feature, we checked if the LLM&amp;rsquo;s response included the word &amp;ldquo;security&amp;rdquo; and described the error. This worked for straightforward cases but broke down quickly. Take a question like, &amp;ldquo;How many Lambdas am I running?&amp;rdquo; The LLM might give the right numbers but skip the word &amp;ldquo;running,&amp;rdquo; failing the test even though the answer was correct.&lt;/p&gt;
&lt;p&gt;These early failures revealed the limitations of keyword-based validation and underscored the need for a more nuanced approach. Inspired by platforms like LangSmith and Promptfoo, we began leveraging LLMs themselves as evaluators. For deterministic tasks, simple keyword checks suffice, but for more complex scenarios—like assessing whether a response answers a specific question—we rely on an &amp;ldquo;LLM Judge.&amp;rdquo; This approach balances efficiency and flexibility, reserving LLM evaluation for cases where it truly adds value. The test suite now integrates both methods, running against every code change to validate response content, accuracy, and format.&lt;/p&gt;
&lt;p&gt;&lt;img src="promptfoo.png" alt="Example PromptFoo Eval"&gt;&lt;/p&gt;
&lt;p&gt;The eval suite keeps getting more robust, which means that when new AI models drop, we can quickly catch any weirdness before it hits production. The generative AI space moves crazy fast and the code changes a lot, but the evals are a safety net - catching hallucinations, maintaining quality, and making sure we don&amp;rsquo;t ship anything that&amp;rsquo;ll annoy our users.&lt;/p&gt;
&lt;p&gt;So, while hallucinations are now much rarer, what about that one with the &lt;code&gt;--force&lt;/code&gt; flag? Yes, it&amp;rsquo;s &amp;ldquo;just another bug,&amp;rdquo; but it taught us something fascinating about these AI errors.&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/codegen-learnings/"&gt;
A Recipe for a Better AI-based Code Generator
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/pulumi-copilot-rest/"&gt;
Announcing the Pulumi Copilot REST API Preview
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/pulumi-copilot/"&gt;
Introducing Pulumi Copilot: Intelligent Cloud Management
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="llms-think-like-humans-sort-of"&gt;LLMs think like humans (sort of)&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;--force&lt;/code&gt; hallucination wasn&amp;rsquo;t totally wrong - it was revealing what users intuitively expect from the CLI, and the LLM accidentally showed us what was missing. Force deletion is a common pattern across developer tools, and the LLM, trained on vast amounts of documentation and code, simply reflects these established conventions.&lt;/p&gt;
&lt;p&gt;This has fundamentally changed how we view hallucinations. While the team works constantly to minimize them – and our eval work means they happen way less frequently – some of them are clearly product signals. The LLM, in this light, becomes an unexpected source of user research, drawing on its training across thousands of developer tools and experiences.&lt;/p&gt;
&lt;p&gt;This insight is one of the key lessons of building Copilot:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Minimize LLM Usage:&lt;/strong&gt; Let traditional code handle deterministic tasks, reserve LLMs for natural language work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decompose into Skills:&lt;/strong&gt; Break complex tasks into modular units that combine LLM and traditional code appropriately&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test Rigorously:&lt;/strong&gt; Use multiple validation approaches, including LLMs testing LLMs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn from Hallucinations:&lt;/strong&gt; Sometimes incorrect outputs reveal user expectations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn from Users Continuously:&lt;/strong&gt; User interactions improve our AI systems - from training better skills to catching hallucinations and revealing product opportunities.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These lessons helped shape our latest release: &lt;strong&gt;&lt;a href="https://www.pulumi.com/blog/pulumi-copilot-rest/"&gt;the Pulumi Copilot REST API&lt;/a&gt;&lt;/strong&gt;, now available in preview. You can integrate these same capabilities and skills into your own tools and workflows. Whether you&amp;rsquo;re building CLI extensions, chat integrations, or automated deployment checks, the API provides a contextual understanding of Copilot. &lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/pulumi-cloud/copilot/api/"&gt;Try it out&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We can&amp;rsquo;t wait to see what you build!&lt;/p&gt;</description><author>Artur Laksberg</author><author>Simon Howe</author><author>Adam Gordon Bell</author><category>copilot</category><category>ai</category><category>infrastructure-as-code</category><category>pulumi-copilot</category></item><item><title>Next-level IaC: Bridging the Declarative Gap</title><link>https://www.pulumi.com/blog/next-level-iac-briding-the-declarative-gap/</link><pubDate>Fri, 07 Jun 2024 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/next-level-iac-briding-the-declarative-gap/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/next-level-iac-briding-the-declarative-gap/index.png" /&gt;
&lt;p&gt;Pulumi stands out in the world of infrastructure-as-code (IaC) for its flexibility and ease of use. This is due to its unique approach of using general purpose programming languages to describe system configuration. It accomplishes this through a small but brilliant implementation detail that bridges the worlds of declarative and standard programming using asynchronous semantics.&lt;/p&gt;
&lt;p&gt;In Pulumi, you describe your infrastructure in code – real code, not a &lt;a href="https://en.wikipedia.org/wiki/Domain-specific_language"&gt;DSL&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/YAML"&gt;YAML&lt;/a&gt; – using your preferred general purpose programming language. You don&amp;rsquo;t have to become a specialist in a niche proprietary declarative language, like Terraform&amp;rsquo;s HCL. Rather, you can reuse your existing programming skills, writing in standard imperative, object-oriented, and even &lt;a href="https://github.com/pulumi/examples/blob/master/aws-fs-s3-folder/Program.fs"&gt;functional language&lt;/a&gt; styles, while still gaining all of the benefits of the declarative style that other tools emphasize.&lt;/p&gt;
&lt;p&gt;Pulumi provides a unique mix of a &lt;a href="https://www.pulumi.com/docs/concepts/how-pulumi-works/#declarative-and-imperative-approach"&gt;declarative model&lt;/a&gt; embedded and implemented inside of a standard programming language, allowing all the flexibility of custom imperative code, while still enabling Pulumi&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/concepts/how-pulumi-works/#deployment-engine"&gt;deployment engine&lt;/a&gt; to infer opportunities for parallel asynchronous execution and to converge a partially-realized system.&lt;/p&gt;
&lt;p&gt;The secret sauce that lets Pulumi achieve this is the &lt;a href="https://www.pulumi.com/docs/concepts/inputs-outputs/apply/"&gt;&lt;code&gt;apply&lt;/code&gt;&lt;/a&gt; function, and the way it handles inputs and outputs via asynchronous programming constructs, similar to &lt;a href="https://en.wikipedia.org/wiki/Futures_and_promises"&gt;futures or promises&lt;/a&gt;. We&amp;rsquo;ll go deeper on that in this article, but first let&amp;rsquo;s talk a little bit about the flow of operations in Pulumi.&lt;/p&gt;
&lt;h2 id="pulumis-architecture-and-flow-of-operations"&gt;Pulumi&amp;rsquo;s architecture and flow of operations&lt;/h2&gt;
&lt;p&gt;Pulumi is a system that is made up of a number of components. At a high level, those are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/concepts/glossary/#program"&gt;&lt;strong&gt;Pulumi Programs&lt;/strong&gt;&lt;/a&gt;: The code you write to describe the infrastructure you need.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/concepts/how-pulumi-works/#deployment-engine"&gt;&lt;strong&gt;Deployment Engine&lt;/strong&gt;&lt;/a&gt;: A process that executes requests to create/modify infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/concepts/how-pulumi-works/#resource-providers"&gt;&lt;strong&gt;Resource Providers&lt;/strong&gt;&lt;/a&gt;: Plugins that interface with cloud services like AWS, Azure, and others.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These components interact together to manage your infrastructure across multiple cloud providers using a consistent programming and execution model.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://www.pulumi.com/blog/next-level-iac-briding-the-declarative-gap/pulumi-architecture-simplified.png"
alt="Figure: Pulumi&amp;rsquo;s Program, Deployment Engine, and Resource Provider architecture"&gt;&lt;figcaption&gt;
&lt;p&gt;Figure: Pulumi&amp;rsquo;s Program, Deployment Engine, and Resource Provider architecture&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Let&amp;rsquo;s get into an example of how information flows back and forth between these components. Suppose you want to write a Pulumi program to create an AWS S3 Bucket to &lt;a href="https://www.pulumi.com/registry/packages/aws/how-to-guides/s3-website/"&gt;host a static website&lt;/a&gt;. When that bucket is created, it will be given a unique identifier that you need to use in other parts of your infrastructure. Every time you create the bucket the identifier will change, and it&amp;rsquo;s generated by the AWS API, so we can&amp;rsquo;t know it ahead of time. We need to handle this as a variable, not a fixed configuration value.&lt;/p&gt;
&lt;p&gt;To do that in a Pulumi program, you can create a bucket and then refer to its &lt;code&gt;id&lt;/code&gt; property later, like this:&lt;/p&gt;
&lt;div&gt;
&lt;pulumi-chooser type="language" options="typescript,python" mode=""&gt;&lt;/pulumi-chooser&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="typescript" mode=""&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@pulumi/pulumi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@pulumi/aws&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Create an S3 bucket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Export the name of the bucket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="python" mode=""&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create an S3 bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Export the name of the bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bucket_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;p&gt;When this program runs, the call to create the &lt;code&gt;bucket&lt;/code&gt; object will issue a request to the &lt;em&gt;deployment engine&lt;/em&gt;. The deployment engine will load the &lt;em&gt;resource provider&lt;/em&gt; for AWS, and ask it to create the S3 bucket. The AWS resource provider then issues the request to the AWS API, which will create the bucket and then return the &lt;code&gt;id&lt;/code&gt; of the newly created resource. The provider will return that to the deployment engine, which will then &lt;em&gt;set the realized value&lt;/em&gt; on your Pulumi program&amp;rsquo;s &lt;code&gt;bucket.id&lt;/code&gt; property and then move on to creating anything that depends on that property.&lt;/p&gt;
&lt;p&gt;This all seems pretty straight-forward, except that Pulumi programs don&amp;rsquo;t wait and imperatively do each operation, one at a time. Instead the deployment engine will run them in parallel, only waiting in cases where the output of one operation is needed to run the next operation. This can create complex structures with some operations needing to wait for inputs from other operations, and some that don&amp;rsquo;t, all finishing at different times, depending on how long the cloud service takes to process that request.&lt;/p&gt;
&lt;h2 id="inputs-and-outputs-as-asynchronous-futures"&gt;Inputs and Outputs as asynchronous futures&lt;/h2&gt;
&lt;p&gt;Infrastructure automation tasks can be long-running and can result in intermittent errors and may need to be retried before moving on to the next step. A robust IaC solution will need to run concurrently, be able to restart, retry, and resume itself, and understand that a set of tasks could happen in any order, not necessarily in the order you defined them in code.&lt;/p&gt;
&lt;p&gt;Back in our Pulumi program code, that means that some of the time the &lt;code&gt;bucket&lt;/code&gt; object from our earlier example has its &lt;code&gt;id&lt;/code&gt; property set, and sometimes it doesn&amp;rsquo;t. We wouldn&amp;rsquo;t want to run any operation that needed that variable before it was set, but we also wouldn&amp;rsquo;t want to wait to start operations that don&amp;rsquo;t need that value.&lt;/p&gt;
&lt;p&gt;To address this, Pulumi models the input and output values as &lt;a href="https://en.wikipedia.org/wiki/Futures_and_promises"&gt;futures&lt;/a&gt;. This allows you to describe a complex set of needs using a familiar value-like syntax. Instead of using a &lt;code&gt;string&lt;/code&gt; result directly, which would only exist some of the time, you instead work with an &lt;a href="https://www.pulumi.com/docs/concepts/inputs-outputs/#outputs"&gt;&lt;code&gt;Output&amp;lt;string&amp;gt;&lt;/code&gt;&lt;/a&gt;, that Pulumi understands as a reference to a value that currently doesn’t exist, but &lt;em&gt;will&lt;/em&gt; exist when the operation completes. Similarly, &lt;a href="https://www.pulumi.com/docs/concepts/inputs-outputs/#inputs"&gt;&lt;code&gt;Input&amp;lt;string&amp;gt;&lt;/code&gt;&lt;/a&gt; describes a dependency on an input string that will &lt;em&gt;at some point&lt;/em&gt; be provided as the result of another operation.&lt;/p&gt;
&lt;p&gt;This notion of &lt;em&gt;future&lt;/em&gt; completion is essential to understanding why &lt;code&gt;apply&lt;/code&gt; blocks are necessary within Pulumi programs. The &lt;code&gt;apply&lt;/code&gt; function is called by the deployment engine only when the operation has completed and the fully realized value is available. By using &lt;code&gt;Input&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;Output&amp;lt;T&amp;gt;&lt;/code&gt; to build the connections between different parts of your infrastructure, the code is leveraging asynchronous value semantics not just to wrap and provide access to a future value, but also to describe a &lt;em&gt;relationship&lt;/em&gt; between two components. This allows Pulumi programs to be very natural to author and as readable as traditional code, while also enabling Pulumi&amp;rsquo;s deployment engine to execute the entirety of your graph of operations in parallel, only waiting when necessary. This maximizes the efficiency of interacting with potentially slow and unreliable external cloud services.&lt;/p&gt;
&lt;h2 id="realizing-values-using-the-apply-function"&gt;Realizing values using the &lt;code&gt;apply&lt;/code&gt; function&lt;/h2&gt;
&lt;p&gt;An important distinction though, is that &lt;code&gt;Input&amp;lt;T&amp;gt;&lt;/code&gt;/&lt;code&gt;Output&amp;lt;T&amp;gt;&lt;/code&gt; values are just declarative placeholders for the values that will eventually be present. At a certain point you will need to access the values directly, for example in the context of a transformation. The output of one operation might produce a string, which you need to transform into another data structure before passing to the next operation. Pulumi really shines in this context!&lt;/p&gt;
&lt;p&gt;Using the &lt;code&gt;apply&lt;/code&gt; function we can pass an &lt;code&gt;Input&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;Output&amp;lt;T&amp;gt;&lt;/code&gt; to a &lt;a href="https://en.wikipedia.org/wiki/Data_transformation_(computing)"&gt;transformation function&lt;/a&gt; which will accept the realized value as its input (and only run when that value is available). Within that function you can handle the string as a normal string, then return it back as an &lt;code&gt;Output&amp;lt;string&amp;gt;&lt;/code&gt;. This allows you to use the full power of your programming language to perform a custom operation inline with the rest of your infrastructure automation.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a simple example of using &lt;code&gt;apply&lt;/code&gt; to transform an output to something new using your programming language&amp;rsquo;s libraries. In this case, &lt;a href="https://en.wikipedia.org/wiki/Base64"&gt;Base64 encoding&lt;/a&gt; the &lt;code&gt;id&lt;/code&gt; of our &lt;code&gt;bucket&lt;/code&gt; object.&lt;/p&gt;
&lt;h3 id="example-using-apply-to-base64-encode-an-output"&gt;Example: Using &lt;code&gt;apply&lt;/code&gt; to Base64 encode an Output&lt;string&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;pulumi-chooser type="language" options="typescript,python" mode=""&gt;&lt;/pulumi-chooser&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="typescript" mode=""&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@pulumi/pulumi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;@pulumi/aws&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Create an S3 bucket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Export the name of the bucket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Export the name of the bucket, in Base64 encoding
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucketNameBase64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Transform the bucket name into a Base64 encoded string
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;base64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="python" mode=""&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create an S3 bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Export the name of the bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bucket_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# A Base64 encoder function to use in the apply function&amp;#39;s lambda&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;base64_encode&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&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ascii&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;base64_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;base64_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64_bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ascii&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Export the name of the bucket, in Base64 encoding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;bucket_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;p&gt;Our example shows how to do Base64 encoding, but this technique can be used for any kind of &lt;a href="https://en.wikipedia.org/wiki/Data_transformation_(computing)"&gt;transformation function&lt;/a&gt;. These can be as elaborate as your use case requires, and leverage the full power of your &lt;a href="https://www.pulumi.com/blog/next-level-iac-package-ecosystems/"&gt;programming language and its entire ecosystem&lt;/a&gt;. Doing custom transformations like this in other popular tools is often not possible. This leaves you stuck using external coordination (e.g. gluing things together with Bash scripts) which breaks their declarative-only model (because you cannot describe everything you need to do in it) and interrupts these tools&amp;rsquo; ability to fully understand your infrastructure&amp;rsquo;s dependency graph.&lt;/p&gt;
&lt;p&gt;While Pulumi&amp;rsquo;s asynchronous semantics may seem somewhat complex for basic use cases, they unlock abilities in more complex use cases, which is when you really need your IaC tooling to show up for you. Because real life is never as simple as demo code.&lt;/p&gt;
&lt;h2 id="next-steps"&gt;Next steps&lt;/h2&gt;
&lt;p&gt;If you haven&amp;rsquo;t already, &lt;a href="https://www.pulumi.com/docs/install/"&gt;install Pulumi&lt;/a&gt; today, and follow our self-directed &lt;a href="https://www.pulumi.com/start"&gt;Getting Started guides&lt;/a&gt; to learn more about making the most of Pulumi&amp;rsquo;s next-level infrastructure management features at your organization.&lt;/p&gt;
&lt;p&gt;To learn more, you can watch the following video which provides a high level overview of how Pulumi works:&lt;/p&gt;
&lt;div class="rounded-md shadow border border-gray-300 w-3/4 mx-auto my-4" style="position: relative; padding-bottom: 40.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe
src="//www.youtube.com/embed/Q8tw6YTD3ac?rel=0"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;"
allowfullscreen=""
title="Introduction to Pulumi in Three Minutes"
srcdoc="&lt;style&gt;*{padding:0;margin:0;overflow:hidden}html,body{height:100%}img{position:absolute;width:100%;top:0;bottom:0;margin:auto}&lt;/style&gt;&lt;a href=https://www.youtube.com/embed/Q8tw6YTD3ac?autoplay=1&gt;&lt;img src='https://www.pulumi.com/images/home/youtube-getting-started.png' alt='Introduction to Pulumi in Three Minutes'&gt;&lt;/a&gt;"&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id="pulumi-cloud"&gt;Pulumi Cloud&lt;/h2&gt;
&lt;p&gt;The Pulumi Cloud is a fully managed service that helps you adopt Pulumi’s open source SDK with ease. It provides built-in state and secrets management, integrates with source control and CI/CD, and offers a web console and API that make it easier to visualize and manage infrastructure. It is free for individual use, with features available for teams.&lt;/p&gt;
&lt;p&gt;&lt;a class="btn btn-secondary" href="https://app.pulumi.com/signup" target="_blank"&gt;Create an Account&lt;/a&gt;&lt;/p&gt;</description><author>Troy Howard</author><category>next-level-iac</category><category>typescript</category><category>python</category><category>architecture</category></item><item><title>Details of the Pulumi Outage on October 6, 2023</title><link>https://www.pulumi.com/blog/post-mortem-2023-10-06/</link><pubDate>Wed, 18 Oct 2023 11:52:58 -0700</pubDate><guid>https://www.pulumi.com/blog/post-mortem-2023-10-06/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/post-mortem-2023-10-06/index.png" /&gt;
&lt;p&gt;At Pulumi, we understand that Pulumi Cloud plays an important role in how our customers address their infrastructure management challenges. As a result, we strive for the highest levels of availability and performance in Pulumi Cloud. Unfortunately, on Friday, October 6, 2023, Pulumi Cloud suffered a 24 minute outage during which we failed to process 74.7% of received requests. In this post, we&amp;rsquo;d like to share our findings on the root cause of this outage, and the steps we are taking to ensure this sort of outage doesn&amp;rsquo;t happen again.&lt;/p&gt;
&lt;p&gt;On October 6th at approximately 17:15 UTC we shipped a database migration modifying foreign keys on a table to our production environment, clearing it for release after testing in several non-production environments and a few rounds of peer review. However, the pre-production testing was not an adequate substitute to test the behavior of the migration when running on our production dataset under full traffic load.&lt;/p&gt;
&lt;p&gt;Adding foreign key constraints to a table can be done “in place” &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html#online-ddl-foreign-key-operations"&gt;with an asterisk&lt;/a&gt;. Testing and review missed that we weren’t abiding by that asterisk. The resulting table copy operation caused by the bad migration held a lock for a significant amount of time, and caused a query pileup that starved our database of all available connections.&lt;/p&gt;
&lt;p&gt;This is the first time in six years that Pulumi has seen an outage of this scale. We are careful not to make changes to high traffic tables on the core API path responsible for handling updates and storing state. When we’ve needed to update these tables in the past, we were heedful in standing up new tables; duplicating writes; and cutting over to new tables without downtime.&lt;/p&gt;
&lt;p&gt;During the review process, we had categorized the migration as low risk, as the affected table is low traffic relative to our other workloads (&amp;lt;0.2% of the traffic of our busiest table). We learned through this incident that we&amp;rsquo;ve arrived at a scale where a block on even a relatively low-traffic table is enough to cause writes into this table to block the entire connection pool and starve the rest of the database and API workloads.&lt;/p&gt;
&lt;p&gt;Within three minutes of the change hitting production, our alarms alerted us to our service degradation and within five minutes, multiple team members were huddled together on an incident call. Our responders parallelized in gaining access to our production database and working to terminate the offending blocked writes that were starving other API workloads. Others assessed impact and worked on customer comms. Unfortunately, high database load also affected the responsiveness of our operational tooling. While we were preparing to execute mitigation actions, the migration completed successfully and both the database and API availability immediately and fully recovered.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/post-mortem-2023-10-06/active-sessions-graph.png" alt="Active Database Sessions Graph"&gt;&lt;/p&gt;
&lt;p&gt;But failing three quarters of our traffic for a twenty minute outage is unacceptable. A migration that caused this level of locking should not have been allowed to run. Following an internal team postmortem, we have identified improvements we will be applying to our Software Development Lifecycle to automatically and accurately assess the impact of proposed migrations, as well as to block dangerous migrations from running in production. These include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;New guidance, enforced by SQL linting, that migrations must always explicitly specify safe &lt;code&gt;ALGORITHM&lt;/code&gt; and &lt;code&gt;LOCK&lt;/code&gt; constraints.&lt;/li&gt;
&lt;li&gt;Improving the fidelity of our pre-production environments to better simulate production workloads.&lt;/li&gt;
&lt;li&gt;New tooling to quickly create ephemeral database performance testing environments at the click of a button. (pulumi makes this easy!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We are also making changes to split parts of our service into different failure domains. This ensures that even if a table on a non-critical path gets blocked, we reserve connections for core workloads. We want the Pulumi Cloud to remain available to continue to save stack state from customer updates even if other parts of the service are failing.&lt;/p&gt;
&lt;p&gt;At Pulumi, we value honesty and transparency. It’s easy to repeat the past; harder to learn from it and move past it. If Pulumi is to be the best developer tools platform on the market, that begins with how we build and operate our software. We deeply apologize to our users for the disruption in service, and we commit ourselves further in driving towards excellence - in both the features we provide and the operational standards to which we hold ourselves.&lt;/p&gt;
&lt;p&gt;Thank you for your understanding, and for sticking with us on the road to better.&lt;/p&gt;</description><author>Casey Huang</author><category>postmortem</category></item><item><title>Converting Bicep code to Pulumi</title><link>https://www.pulumi.com/blog/converting-bicep-to-pulumi/</link><pubDate>Fri, 29 Sep 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/converting-bicep-to-pulumi/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/converting-bicep-to-pulumi/index.png" /&gt;
&lt;p&gt;Bicep is a DSL developed by Microsoft to simplify the authoring of &lt;a href="https://www.pulumi.com/docs/iac/adopting-pulumi/migrating-to-pulumi/from-arm/"&gt;ARM templates&lt;/a&gt; and deploy resources to Azure. Today I will be sharing with you a new Pulumi converter plugin that I have been working on that converts Bicep code to any of the supported Pulumi languages.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/VNj1EyQ7wug?rel=0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep"&gt;Bicep&lt;/a&gt; language is a domain-specific language for Infrastructure-As-Code built by Microsoft that uses declarative syntax to deploy Azure resources. It was made to address the shortcomings of authoring ARM templates that are written in JSON. Compared to ARM templates, Bicep is much more concise and readable. However, here at Pulumi we believe that using general-purpose programming languages that are familiar to developers is the best way to author Infrastructure-As-Code. Besides being able to use a general-purpose language, you and your team can benefit from using Pulumi Cloud as you scale to larger projects and teams with features such as &lt;a href="https://www.pulumi.com/blog/pulumi-deployments/"&gt;Pulumi Deployments&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/organization-access-tokens/"&gt;Organization Access Tokens&lt;/a&gt;, &lt;a href="https://www.pulumi.com/blog/review-stacks/"&gt;Review Stacks&lt;/a&gt; and more.&lt;/p&gt;
&lt;p&gt;That is why I have been working on a new &lt;a href="https://github.com/pulumi/pulumi-converter-bicep"&gt;Pulumi converter plugin&lt;/a&gt; that converts Bicep code to any of the supported Pulumi languages using the Pulumi CLI via &lt;code&gt;pulumi convert&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="installing-and-using-the-plugin"&gt;Installing and using the plugin&lt;/h2&gt;
&lt;p&gt;First, install the &lt;em&gt;converter&lt;/em&gt; plugin using the Pulumi CLI:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi plugin install converter bicep
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command will install the plugin from the latest GitHub release of the &lt;a href="https://github.com/pulumi/pulumi-converter-bicep"&gt;repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once installed, you can use it to convert Bicep code to Pulumi. For example, let&amp;rsquo;s say we have the following Bicep code in a file called &lt;code&gt;example.bicep&lt;/code&gt; which deploys a storage account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bicep" data-lang="bicep"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;storage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Microsoft.Storage/storageAccounts@2022-09-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;storageaccount&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;resourceGroup&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nv"&gt;location&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;StorageV2&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Standard_LRS&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Navigate to the directory where the file is located and run the following command. For example to generate TypeScript code from the Bicep code above, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulumi convert --from bicep --language typescript --out output -- --entry example.bicep
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command will use the Bicep converter plugin we just installed to convert the code in &lt;code&gt;example.bicep&lt;/code&gt; to TypeScript and place the generated code in the &lt;code&gt;output&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;You can change &lt;code&gt;typescript&lt;/code&gt; to any of the supported languages such as &lt;code&gt;csharp&lt;/code&gt;, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;go&lt;/code&gt;, &lt;code&gt;java&lt;/code&gt; or &lt;code&gt;yaml&lt;/code&gt; and you are good to go.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t specify the &lt;code&gt;--entry&lt;/code&gt; flag, the plugin will look for the first file with the &lt;code&gt;.bicep&lt;/code&gt; extension in the current directory.&lt;/p&gt;
&lt;p&gt;Let us look at the generated code from the example above:&lt;/p&gt;
&lt;div&gt;
&lt;pulumi-chooser type="language" options="typescript,python,go,csharp,java,yaml" mode=""&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="typescript" mode=""&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/pulumi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;azure_native&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/azure-native&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// The name of the resource group to operate on
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resourceGroupName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;resourceGroupName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azure_native&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getResourceGroupOutput&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resourceGroupName&lt;/span&gt;: &lt;span class="kt"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;azure_native&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StorageAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;accountName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;StorageV2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;location&lt;/span&gt;: &lt;span class="kt"&gt;currentResourceGroup.apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resourceGroupName&lt;/span&gt;: &lt;span class="kt"&gt;currentResourceGroup.apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Standard_LRS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="python" mode=""&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_azure_native&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;azure_native&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# The name of the resource group to operate on&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;resource_group_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;resourceGroupName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;current_resource_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;azure_native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_resource_group_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource_group_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource_group_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;azure_native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StorageAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;StorageV2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;current_resource_group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resource_group_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;current_resource_group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;azure_native&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkuArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Standard_LRS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="go" mode=""&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/pulumi/pulumi-azure-native-sdk/v2/resources&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/pulumi/pulumi-azure-native-sdk/v2/storage&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/pulumi/pulumi/sdk/v3/go/pulumi&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/pulumi/pulumi/sdk/v3/go/pulumi/config&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The name of the resource group to operate on&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;resourceGroupName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LookupResourceGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LookupResourceGroupArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewStorageAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StorageAccountArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AccountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;StorageV2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SkuArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Standard_LRS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="csharp" mode=""&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Pulumi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;AzureNative&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AzureNative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// The name of the resource group to operate on&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resourceGroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;resourceGroupName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentResourceGroup&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AzureNative&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AzureNative&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StorageAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;AccountName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Kind&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;StorageV2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Sku&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AzureNative&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkuArgs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Standard_LRS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="java" mode=""&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;generated_program&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.Context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.Pulumi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.core.Output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.azurenative.resources.ResourcesFunctions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.azurenative.resources.inputs.GetResourceGroupArgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.azurenative.storage.StorageAccount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.azurenative.storage.StorageAccountArgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;com.pulumi.azurenative.storage.inputs.SkuArgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.nio.file.Files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.nio.file.Paths&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;resourceGroupName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ResourcesFunctions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getResourceGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetResourceGroupArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StorageAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;storage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StorageAccountArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;storageaccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;StorageV2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;applyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;applyValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getResourceGroupResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SkuArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Standard_LRS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="language" values="yaml" mode=""&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageaccount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azure-native:storage:StorageAccount&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;storageaccount&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;StorageV2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${currentResourceGroup.location}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${currentResourceGroup.name}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Standard_LRS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;currentResourceGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::invoke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azure-native:resources:getResourceGroup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${resourceGroupName}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;/pulumi-chooser&gt;
&lt;/div&gt;
&lt;p&gt;Notice how the in the original Bicep code, we reference the location of the &lt;em&gt;implicit&lt;/em&gt; resource group via &lt;code&gt;resourceGroup().location&lt;/code&gt;. When the converter generates code for the target language, it replaces this &lt;em&gt;implicit&lt;/em&gt; resource group with an explicit one by parameterizing the program by a resource group name and using that resource group when deploying the resources.&lt;/p&gt;
&lt;h2 id="early-days-for-the-converter"&gt;Early days for the converter&lt;/h2&gt;
&lt;p&gt;Currently the converter supports most of the features of Bicep such as parameters, variables, modules, resources, outputs and a number of built-in functions. That said, the converter plugin is still in its early days and it&amp;rsquo;s not yet tested against many real-world Bicep programs. I am actively working on improving it and making it support most of the Bicep language features. The source code is available on this &lt;a href="https://github.com/pulumi/pulumi-converter-bicep"&gt;GitHub repository&lt;/a&gt;. If you find any issues or have any feedback, please open an issue on the repository.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;The converter plugin is a native binary built using .NET in &lt;a href="https://dotnet.microsoft.com/en-us/languages/fsharp"&gt;F#&lt;/a&gt; and new Pulumi Converter SDK. Using the core &lt;a href="https://www.nuget.org/packages/Pulumi"&gt;Pulumi&lt;/a&gt; nuget package, we shipped an experimental API that allows you to easily build converter plugins that can extend the functionality of &lt;code&gt;pulumi convert&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Building a language converter plugin involves taking the source language, in this case Bicep, and converting it to our internal Pulumi Configuration Language, or PCL for short. The converter implements a function of the following shape:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;convertProgram&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ConvertProgramRequest&lt;/span&gt;&lt;span class="o"&gt;):&lt;/span&gt; &lt;span class="n"&gt;ConvertProgramResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sourceDirectory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SourceDirectory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;targetDirectory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TargetDirectory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// conversion logic here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In our case, we read the Bicep code from the source directory, convert it to PCL and write out the PCL code in the target directory. Then, Pulumi uses the built-in program generation facilities to take care of the rest of the work of generating the target language code from the PCL code so that we don&amp;rsquo;t have to worry about language-specific details. The converter only needs to know how to generate PCL.&lt;/p&gt;
&lt;p&gt;To build the actual transformation from Bicep to PCL, I made use the of the &lt;a href="https://www.nuget.org/packages/Azure.Bicep.Core"&gt;Azure.Bicep.Core&lt;/a&gt; package available for .NET which allowed me to parse the Bicep code and generate a typed Abstract Syntax Tree (AST) from it. Building an AST from the source language allows us to easily traverse the tree, analyze it and symbolically rewrite pieces of it. Working at the AST level also makes it easy to test source code transformation using structure rather than text. Once we have obtained the Bicep AST, we transform it into a Pulumi AST that represents a PCL program. Finally, we print out the Pulumi AST to a string and write it to the target directory.&lt;/p&gt;
&lt;p&gt;If you are curious about the specification of the AST that represents Bicep programs, head over to &lt;a href="https://github.com/Zaid-Ajaj/pulumi-converter-bicep/blob/master/src/Converter/BicepParser.fs"&gt;this file&lt;/a&gt; from the source code. You will find types such as &lt;code&gt;BicepProgram&lt;/code&gt; and &lt;code&gt;BicepSyntax&lt;/code&gt; that model almost every aspect of Bicep code. As for the AST of PCL programs, you can find the type definitions &lt;a href="https://github.com/Zaid-Ajaj/pulumi-converter-bicep/blob/master/src/Converter/PulumiTypes.fs"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The F# language is great for almost everything, but is especially amazing for writing language converters because of its powerful pattern-matching capabilities and the ability to write code in a functional style. Many of us working at Pulumi are big fans of F#!&lt;/p&gt;
&lt;p&gt;Here is a snippet of the transformation pipeline written in F# that takes the input Bicep program and converts it to the final PCL text&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;bicepProgram&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;BicepProgram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dropResourceUnknowns&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;BicepProgram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reduceScopeParameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;BicepProgram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameterizeByTenantAndSubscriptionId&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;BicepProgram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addResourceGroupNameParameterToModules&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;BicepProgram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameterizeByResourceGroup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bicepProgramToPulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modifyComponentPaths&lt;/span&gt; &lt;span class="n"&gt;rewriteComponentPath&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Printer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printProgram&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can learn more on the implementation of these functions from the source code on GitHub. I&amp;rsquo;ve written it in a way that is easy to follow, understand and contribute to. Most of these functions are fully &lt;a href="https://github.com/Zaid-Ajaj/pulumi-converter-bicep/blob/master/src/Tests/Program.fs"&gt;unit-tested&lt;/a&gt; and I am working on adding more tests to cover more of the Bicep language features.&lt;/p&gt;
&lt;h2 id="bonus-converter-arm-to-pulumi"&gt;Bonus converter: ARM to Pulumi&lt;/h2&gt;
&lt;p&gt;Those who are familiar with the Bicep CLI, know that it has a built-in &lt;em&gt;decompiler&lt;/em&gt; that converts ARM templates to Bicep. I thought it would be fun to build another converter that takes ARM templates to Pulumi but without writing any conversion logic for it. Instead, I would decompile the ARM template to Bicep, then use the logic from the Bicep converter to generate the final PCL code. Fortunately, the decompiler code is embedded in the nuget package &lt;a href="https://www.nuget.org/packages/Azure.Bicep.Core"&gt;Azure.Bicep.Core&lt;/a&gt; so we don&amp;rsquo;t have to rely on the existence of the Bicep CLI in order to make this converter work.&lt;/p&gt;
&lt;p&gt;From all of this, I created another converter &lt;code&gt;pulumi-converter-arm&lt;/code&gt; that works exactly like the Bicep converter but takes ARM templates as input. Head over to the &lt;a href="https://github.com/Zaid-Ajaj/pulumi-converter-arm"&gt;repository&lt;/a&gt; to learn more about how to install it and how to use it.&lt;/p&gt;
&lt;p&gt;Depending on how well this ARM converter does its job, we might consider deprecating &lt;a href="https://www.pulumi.com/arm2pulumi/"&gt;arm2pulumi&lt;/a&gt; in favor of it. However, we first need to test it against more templates and iron out the rough edges. It would be the ideal situation because there are virtually zero maintenance costs, any improvements to the Bicep converter would automatically get picked up here. Also, improvements to the decompiler from the &lt;a href="https://www.nuget.org/packages/Azure.Bicep.Core"&gt;Azure.Bicep.Core&lt;/a&gt; nuget package would benefit the converter as well.&lt;/p&gt;
&lt;h2 id="building-your-own-pulumi-language-converter"&gt;Building your own Pulumi language converter&lt;/h2&gt;
&lt;p&gt;Converter plugins are a great way to extend the Pulumi CLI with &lt;code&gt;pulumi convert&lt;/code&gt; to support new languages. When we shipped support for &lt;a href="https://www.pulumi.com/blog/converting-full-terraform-programs-to-pulumi/"&gt;Converting full Terraform Programs To Pulumi&lt;/a&gt;, we extended the &lt;code&gt;pulumi convert&lt;/code&gt; command to allow installing and using converter plugins that are shipped independently from the Pulumi CLI.&lt;/p&gt;
&lt;p&gt;In essence, those plugins are executable binaries that can be written in any language, which serves up a gRPC server that implements the &lt;code&gt;Converter&lt;/code&gt; contract:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Converter is a service for converting between other ecosystems and Pulumi.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// This is currently unstable and experimental.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;Converter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ConvertState converts state from the target ecosystem into a form that can be imported into Pulumi.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ConvertState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConvertStateRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConvertStateResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ConvertProgram converts a program from the target ecosystem into a form that can be used with Pulumi.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ConvertProgram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConvertProgramRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConvertProgramResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;This Converter interface is considered experimental and is subject to change in the future.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, learning how to setup gRPC servers and implement the &lt;code&gt;Converter&lt;/code&gt; contract is not an easy task unless you are familiar with it. For .NET, we shipped the &lt;em&gt;experimental&lt;/em&gt; Pulumi converter SDK to the main Pulumi nuget package that makes it extremely easy to build converter plugins.&lt;/p&gt;
&lt;p&gt;A bare-bones converter plugin that does nothing looks like this in F#:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Experimental.Converter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Codegen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;convertProgram&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ConvertProgramRequest&lt;/span&gt;&lt;span class="o"&gt;):&lt;/span&gt; &lt;span class="n"&gt;ConvertProgramResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// TODO: Implement the conversion logic here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nn"&gt;ConvertProgramResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;convertProgram&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Converter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateSimple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Converter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serve&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AwaitTask&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSynchronously&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will spin up a gRPC server that implements the &lt;code&gt;Converter&lt;/code&gt; contract and will serve it on a random port assigned during startup. The Pulumi converter SDK automatically implements gRPC reflection which allows tools such as &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt; to discover the available gRPC services and methods and send example requests to them. See &lt;a href="https://blog.postman.com/postman-now-supports-grpc/"&gt;Postman with gRPC&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="publishing-converter-plugins"&gt;Publishing converter plugins&lt;/h2&gt;
&lt;p&gt;The easiest way to make your converter plugin available to others is to publish it on GitHub releases. The Pulumi CLI expects a naming convention &lt;code&gt;pulumi-converter-&amp;lt;name&amp;gt;&lt;/code&gt; and will automatically download the plugin from the latest GitHub release of the repository when given the &lt;code&gt;--server&lt;/code&gt; flag during &lt;code&gt;pulumi plugin install converter &amp;lt;name&amp;gt; --server &amp;lt;url&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Converter plugins written in .NET are simple console applications that are compiled to native binaries for each target platform. To see how this is done, refer to this &lt;a href="https://github.com/Zaid-Ajaj/pulumi-converter-bicep/blob/master/build/Program.fs#L197"&gt;createAndPublishArtifacts&lt;/a&gt; function written for the Bicep converter that does the heavy lifting of building the native binaries for each platform, putting them in an archive then publishing them to GitHub releases.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: when publishing native binaries for macOS, the Github Action executing the publishing process must be using a macOS runner so that the generated binary from &lt;code&gt;dotnet publish&lt;/code&gt; gets &lt;em&gt;signed&lt;/em&gt; with a developer certificate. This is required for the binary to be executable on macOS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="final-thoughts"&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;I hope you enjoyed this article and learned something new about the Pulumi converter plugins and Pulumi internals. I am very excited about the Bicep converter plugin and I am looking forward to hearing your feedback on it from the community. It was a lot of fun writing this in F# and made writing complex pieces of code very trivial.&lt;/p&gt;
&lt;p&gt;If you have any questions or feedback, don&amp;rsquo;t hesitate to reach out!&lt;/p&gt;</description><author>Zaid Ajaj</author><category>community</category><category>bicep</category><category>azure</category><category>convert</category></item><item><title>Benchmarking Python Performance</title><link>https://www.pulumi.com/blog/benchmarking-python-performance/</link><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/benchmarking-python-performance/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/benchmarking-python-performance/index.png" /&gt;
&lt;p&gt;This is the second post in a series about performance optimizations we&amp;rsquo;ve made
to the Pulumi CLI and SDKs. In this post, we&amp;rsquo;ll go deep on a performance
improvement we made for Pulumi Python programs. You can read more
about Amazing Performance in
&lt;a href="https://www.pulumi.com/blog/amazing-performance/"&gt;the first post in the series&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Late last year, we took a hard look at the performance of Python programs when we
realized they weren&amp;rsquo;t performing up to our expectations. We uncovered a major
bug limiting Python performance, and we ran a number of rigorous experiments
to evaluate just how performant Pulumi Python programs are after the bug had
been repaired. The results indicate Pulumi Python programs are significantly
faster than they were, and now Pulumi Python has reached performance parity
with Pulumi Node.js!&lt;/p&gt;
&lt;h2 id="the-bug"&gt;The Bug&lt;/h2&gt;
&lt;p&gt;When you execute a Pulumi program, Pulumi internally builds a dependency graph
between the resources in your program. In every Pulumi program, some resources
have all their input arguments available at the time of their construction.
In contrast, other resources may depend on &lt;code&gt;Outputs&lt;/code&gt; from other resources.&lt;/p&gt;
&lt;p&gt;For example, consider a sample program where we create two AWS S3 buckets, where
one bucket is used to store logs for the other bucket:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;log_bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;logBucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;log-delivery-write&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;private&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;loggings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BucketLoggingArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;target_bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;log_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;target_prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;log/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because &lt;code&gt;bucket&lt;/code&gt; takes an &lt;code&gt;Output&lt;/code&gt; from &lt;code&gt;log_bucket&lt;/code&gt; as an input,
we can&amp;rsquo;t create the &lt;code&gt;bucket&lt;/code&gt; until after the &lt;code&gt;log_bucket&lt;/code&gt;
is created. We have to create the &lt;code&gt;log_bucket&lt;/code&gt; first to compute its ID,
which we can pass to &lt;code&gt;bucket&lt;/code&gt;. This idea extends inductively for
arbitrary programs – before any resource can be run, we must resolve the
&lt;code&gt;Outputs&lt;/code&gt; of all of its arguments. To do this, Pulumi builds a dependency graph
between all resources in your program. Then, it walks the graph topologically
to schedule provisioning operations.&lt;/p&gt;
&lt;p&gt;Provisioning operations that are not dependent on each other can be executed
in parallel, and Pulumi defaults to unbounded parallelism, but users can
ratchet this down if they so desire. Consider this embarrassingly parallel
Python program:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SQS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="s1"&gt;&amp;#39;pulumi-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SNS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="s1"&gt;&amp;#39;pulumi-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this program, we can create 200 resources in parallel because none of them
take inputs from other resources. This program should be entirely
network-bound because Pulumi can issue all 200 API calls in parallel and wait
for AWS to provision the instances.
&lt;a href="https://github.com/pulumi/pulumi/issues/11116"&gt;We discovered&lt;/a&gt;, however,
that it did not! Strangely, API calls were issued in an initial batch of 20;
as one completed, another would start.&lt;/p&gt;
&lt;h2 id="the-fix"&gt;The Fix&lt;/h2&gt;
&lt;p&gt;The culprit was the Python default future executor,
&lt;a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor"&gt;ThreadPoolExecutor&lt;/a&gt;.
We observed that benchmark was run on a four-core computer, and in Python 3.5
to Python 3.7, the number of max workers is five times the number of cores, or 20
(in Python 3.8, this number was changed to &lt;code&gt;min(32, os.cpu_count() + 4)&lt;/code&gt;). We
realized we shouldn&amp;rsquo;t be using the default &lt;code&gt;ThreadExecutor&lt;/code&gt;, and instead we
should provide a &lt;code&gt;ThreadExecutor&lt;/code&gt; with an adjusted number of &lt;code&gt;max_workers&lt;/code&gt;
based on the configured parallelism value. That way, when users run
&lt;code&gt;pulumi up --parallel&lt;/code&gt;, which issues an upper bound on parallel resource
construction, the &lt;code&gt;ThreadExecutor&lt;/code&gt; will respect that bound. We
&lt;a href="https://github.com/pulumi/pulumi/pull/11122"&gt;merged a fix&lt;/a&gt;
that would plumb the value of &lt;code&gt;--parallel&lt;/code&gt; through to a custom &lt;code&gt;ThreadExecutor&lt;/code&gt;
and measured the impact this change had on the performance of our benchmark.&lt;/p&gt;
&lt;h2 id="experimental-setup"&gt;Experimental Setup&lt;/h2&gt;
&lt;p&gt;We designed and implemented two independent experiments to evaluate this change.
The first experiment measures how well the patched Python runtime stacks up
against the control group, Pulumi Python without the patch. The second experiment
compares Pulumi Python to Pulumi TypeScript using the same benchmark ported
to TypeScript. We used the awesome benchmarking tool
&lt;a href="https://github.com/sharkdp/hyperfine"&gt;hyperfine&lt;/a&gt; to record wall clock time as
our indicator of performance.&lt;/p&gt;
&lt;p&gt;The experiments ran overnight on a 2021 MacBook Pro with 32GB RAM, the M1 chip,
and 10 cores. Experimental code is
&lt;a href="https://github.com/pulumi/python-concurrency-experiments/tags"&gt;available on GitHub&lt;/a&gt;,
and release tags pin the version of the code used for each experiment.
We also made an effort to run the experiments on a quiet machine connected
to power. For all experiment groups, &lt;code&gt;--parallel&lt;/code&gt; was unset, translating to
unbounded parallelism.&lt;/p&gt;
&lt;p&gt;Before between samples, we ran &lt;code&gt;pulumi destroy –yes&lt;/code&gt; to ensure a fresh
environment. Hyperfine measures shell startup time and subtracts the value
before final measurements are recorded to more precisely represent the true
cost of execution. All groups collected 20 samples each. We also discard
&lt;code&gt;stderr&lt;/code&gt; and &lt;code&gt;stdout&lt;/code&gt; to reduce noise associated with logging to a tty, but
we do record the status code of each command so can show they executed successfully.&lt;/p&gt;
&lt;h2 id="python-pre--and-post-patch"&gt;Python: Pre- and Post-patch&lt;/h2&gt;
&lt;p&gt;This experiment compares the performance of Pulumi Python before and after
the patch was applied. The control group used Pulumi v3.43.1, while the
experimental group used Pulumi v3.44.3. The primary difference between these
two groups is that a fix was introduced for a Python runtime concurrency bug
as part of v3.44.0. Both groups use the same benchmark program, which created
100 AWS SNS and 100 AWS SQS resources in parallel, as described earlier. Only
the version of the Pulumi CLI is different between groups.&lt;/p&gt;
&lt;h3 id="control-vs-fix"&gt;&lt;a href="https://app.warp.dev/block/rk7fFf2jn2iKXYcIXwhZ8F"&gt;Control vs. Fix&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard Deviation&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;222.232 s&lt;/td&gt;
&lt;td&gt;0.908 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Experimental&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;70.189 s&lt;/td&gt;
&lt;td&gt;1.497 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; The &lt;strong&gt;Experimental Group&lt;/strong&gt; ran 3.17 ± 0.07 times faster than the &lt;strong&gt;Control Group&lt;/strong&gt;, accounting for a +300% speedup in performance. Running Welch T-Test indicated statical significance (p = 2.93e-59, α=0.05).&lt;/p&gt;
&lt;h2 id="python-vs-typescript"&gt;Python vs. TypeScript&lt;/h2&gt;
&lt;p&gt;After seeing very promising results from the first experiment, we wanted to
determine just how promising these results were. We decided to compare Pulumi
Python to Pulumi TypeScript to see if this fix had narrowed the gap in
performance between the two runtimes. We ported the Python program to TypeScript:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/pulumi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/aws&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// SQS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`pulumi-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// SQS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`pulumi-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For this experiment, we fixed the version of the CLI to v3.44.3, which included
the patch to the Python runtime. Here are the result.&lt;/p&gt;
&lt;h3 id="typescript-vs-python"&gt;&lt;a href="https://app.warp.dev/block/rk7fFf2jn2iKXYcIXwhZ8F"&gt;TypeScript vs. Python&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard Deviation&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;70.975 s&lt;/td&gt;
&lt;td&gt;0.909 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;73.741 s&lt;/td&gt;
&lt;td&gt;1.574 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; The &lt;strong&gt;Python Group&lt;/strong&gt; performed the best and ran 1.04 ± 0.03 times
faster than the &lt;strong&gt;TypeScript Group&lt;/strong&gt;. This accounts for a 4% difference in
performance. A second T-Test indicated statical significance
(p = 1.4e-07, α=0.05). Not only did Python close the gap with TypeScript,
but it is also now marginally faster than its Node.js competitor.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s rare to have a small PR result in such a massive performance increase,
but when it happens, we want to shout it from the rooftops. This change, which
shipped last year in v3.44.3, does not require Python users to opt-in; their programs
are now faster. This patch has closed the gap with the Node.js runtime.
Users can now expect highly parallel Pulumi programs to run in a similar
amount of time between either language.&lt;/p&gt;
&lt;h2 id="artifacts"&gt;Artifacts&lt;/h2&gt;
&lt;p&gt;You can check out the artifacts of the experiments
&lt;a href="https://github.com/pulumi/python-concurrency-experiments/tags"&gt;on GitHub&lt;/a&gt;,
including the source code.&lt;/p&gt;
&lt;p&gt;Here are some useful links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pulumi/python-concurrency-experiments/tags"&gt;The GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pulumi/python-concurrency-experiments/releases/tag/parallelism"&gt;Artifacts from the first experiment&lt;/a&gt;, &amp;ldquo;Control vs. Fix&amp;rdquo; or &amp;ldquo;Pre- and Post-patch&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.warp.dev/block/F6KkbWHvDVWLwtYFKq08Q2"&gt;More statistics&lt;/a&gt; about the first experiment.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pulumi/python-concurrency-experiments/releases/tag/TypeScript-vs-Python"&gt;Artifacts from the second experiment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.warp.dev/block/gspCIKn10y9bEvZDMWHe4Q"&gt;More statistics&lt;/a&gt; about the second experiment.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/"&gt;Pulumi Internals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Justin Van Patten</author><author>Robbie McKinstry</author><category>performance</category><category>platform</category><category>engineering</category></item><item><title>Read Every Single Error</title><link>https://www.pulumi.com/blog/reducing-our-error-rate/</link><pubDate>Tue, 21 Mar 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/reducing-our-error-rate/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/reducing-our-error-rate/index.png" /&gt;
&lt;p&gt;At Pulumi we read every single error message that our API produces. This is the primary mechanism that led to a 17x YoY reduction in our error rate. You’re probably wondering how reading error messages make them go away.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/reducing-our-error-rate/error_rate.png" alt="Pulumi Service API Error Rate Graph"&gt;&lt;/p&gt;
&lt;p&gt;Doesn’t common wisdom tell us that we need a fancy observability toolchain, or to follow the Google SRE model? I can confidently say that you don’t. I’ll go a step further and state that throughout my career, every system I’ve worked on that relied on aggregate views of errors was a complete dumpster fire. In every team where we instead chose to read all the errors, reliability naturally improved over time.&lt;/p&gt;
&lt;p&gt;I offer a concrete process that will drive your error rates down over time with math to back it up.&lt;/p&gt;
&lt;h2 id="read-every-error-message-that-your-system-produces"&gt;Read Every Error Message That Your System Produces&lt;/h2&gt;
&lt;p&gt;You should read every error message that your system produces. Simple but effective. Our team pumps every 5XX into a Slack channel and reviewing each of these is a top priority for the current on-call engineer. There’s a little more to it, but that’s the gist! Commit to this process and your error rates are guaranteed to drop. And I can prove it!&lt;/p&gt;
&lt;h2 id="reliability-from-first-principles"&gt;Reliability from First Principles&lt;/h2&gt;
&lt;p&gt;Why does reading error messages imply improving error rates? It isn&amp;rsquo;t magic. You still have to dedicate time to fix the bugs you&amp;rsquo;re shipping and make proactive investments. But you can model this process with a simple inequality:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(API Call Volume) * (Error Rate) * (Time to Triage an Error) &amp;lt; On-Call Attention
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This comes with a few important constraints:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On-call attention is a fixed commodity&lt;/li&gt;
&lt;li&gt;API call volume scales with your business&lt;/li&gt;
&lt;li&gt;This process requires reading every error message&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The end result is simple. Your error rate must improve over time to keep the scales balanced. If it doesn’t, the process becomes untenable.&lt;/p&gt;
&lt;p&gt;Let’s assume you have one engineer on call, and they spend at most an hour a day triaging errors (they have other responsibilities too!) on each of the ~250 business days per year. That’s a cap of 250 hours per year that can be spent triaging errors. This is effectively fixed. Sure you can hire and split systems out into separate on-call rotations to increase capacity, but our goal is to scale exponentially with respect to humans, not linearly!&lt;/p&gt;
&lt;p&gt;Let’s say that triaging an error is a 5-minute process that might involve any of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checking the issue tracker for a pre-existing ticket to add additional context, a +1, etc.&lt;/li&gt;
&lt;li&gt;Reaching out to a teammate who just shipped some buggy code so they can get a fix out&lt;/li&gt;
&lt;li&gt;Filing a well-documented bug&lt;/li&gt;
&lt;li&gt;Starting a Slack thread with the team to raise a known issue that seems to be cropping up more regularly&lt;/li&gt;
&lt;li&gt;Just opening a PR if it’s a simple fix&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remember we only have 250 hours or 15,000 minutes per year to triage with a single on-call engineer. At five minutes a pop we can triage ~3000 errors per year until things start dropping on the floor.&lt;/p&gt;
&lt;p&gt;Now imagine you work on a new product and you just launched your MVP. It is early days, but there is some traction and you’re seeing API traffic at a rate of 1,000,000 requests per year. Your annual budget for triaging as we previously determined is 3000 errors, which yields a maximum permissible error rate of 0.3%, or a 99.7% success rate.&lt;/p&gt;
&lt;p&gt;Over the next six months, the team iterates, listens to customers, and delivers a ton of value. As a result, traffic levels grow to a rate of 10,000,000 requests per year. But still, our team hasn’t grown much and we still have just one engineer on call at any given point in time meaning our error triage capacity remains fixed at 3000 errors per year. In order to keep up with triaging the error stream at the increased levels of traffic, the team must improve the error rate from 0.3% to 0.03%. And hopefully, this team continues to be successful, increasing API traffic superlinearly in years to come.&lt;/p&gt;
&lt;p&gt;If you want to be able to read every error message, then the error rate has to come down as API traffic increases.&lt;/p&gt;
&lt;h2 id="why-should-you-care"&gt;Why Should You Care?&lt;/h2&gt;
&lt;p&gt;Pulumi aspires to be the most reliable infrastructure that our customers interact with, and the benefits towards that end are reason enough for us. But this process is by no means free, and there is always an opportunity cost.&lt;/p&gt;
&lt;p&gt;However, we noticed a powerful second-order effect emerge over time. The team began obsessing over the user experience. Following this process builds a visceral understanding of how your system behaves. You know when a new feature has a bug before the first support tickets get opened. You know which customer workloads will require scaling investments in the next 90 days. You know what features see heavy usage and which ones customers ignore. You’re forced to confront every wart in your application. Slowly, your team builds a better understanding of your customers and this trickles down into every aspect of product development. You begin to hold yourselves to higher standards.&lt;/p&gt;
&lt;p&gt;A friend and engineer at a large-cap software company read a draft of this post and told me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I can’t imagine this process being set on any of the systems I work on at [redacted]. They are so prone to 500s and everybody shrugs their shoulders as if it is just commonplace.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apathy in place of customer obsession is not an option if you are a startup that wants to disrupt anything.&lt;/p&gt;
&lt;h2 id="the-sres-folly"&gt;The SRE&amp;rsquo;s Folly&lt;/h2&gt;
&lt;p&gt;Error budgets and the SRE model are haute couture. Some preach that we should never look at errors at this level of granularity and instead use expensive tools that aggregate, categorize, and collect statistics on errors flowing through your system. But all of this automation can actually make things worse when you reach for it prematurely. Aggregating errors is a great way to gloss over important details early on. Collecting fancy metrics does not matter if your users are not happy. Cutting your teeth with the tools and processes that make sense for your level of scale is the only way to build a high-performance culture. Skipping straight to step 100 does not always help.&lt;/p&gt;
&lt;p&gt;Admittedly, this process does not work for Google-level scale. But it works a lot longer than you might imagine. Pulumi manages a meaningful percentage of the resources deployed across all clouds. I asked large-cap software engineer about traffic levels and believe it or not, it is in the same order of magnitude as what we see at Pulumi.&lt;/p&gt;
&lt;p&gt;We’re still reading every error.&lt;/p&gt;</description><author>Evan Boyle</author><category>cloud-engineering</category></item><item><title>Achieving Amazing Performance in the Pulumi CLI</title><link>https://www.pulumi.com/blog/amazing-performance/</link><pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/amazing-performance/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/amazing-performance/index.png" /&gt;
&lt;p&gt;This is the first post in a series about performance optimizations we&amp;rsquo;ve made to the Pulumi CLI. Over the last six months at Pulumi, the Platform Team has been working on a project we call &amp;ldquo;Amazing Performance.&amp;rdquo; Amazing Performance is a new initiative to improve the throughput and latency of the Pulumi CLI not only for power users but for everyone. By the end of June 2022, we assembled a list of issues containing both high-value improvements requiring a sizable investment and low-hanging fruit for quick wins. The full list, including the items we have yet to tackle, is contained in &lt;a href="https://github.com/pulumi/pulumi/issues/11598"&gt;a tracking issue on GitHub&lt;/a&gt;. This blog series will cover the highlights.&lt;/p&gt;
&lt;p&gt;This post has two sections. First, we&amp;rsquo;ll describe the tools we built previously to track performance. Secondly, we&amp;rsquo;ll recap a few quick wins. Future posts will detail our major wins as part of the Amazing Performance initiative.&lt;/p&gt;
&lt;h2 id="measuring-and-tracking-performance"&gt;Measuring and Tracking Performance&lt;/h2&gt;
&lt;p&gt;The Amazing Performance initiative didn&amp;rsquo;t come from a vacuum. We&amp;rsquo;ve been working to understand and improve the performance of the Pulumi CLI for years. Much of the earlier effort has focused on gaining performance &lt;em&gt;insights&lt;/em&gt; – building an understanding of where our performance pains were the sharpest. The two most impactful tools at our disposal for understanding performance pains are our analytics dashboard and our OpenTracing support.&lt;/p&gt;
&lt;h3 id="analytics-dashboard"&gt;Analytics Dashboard&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve built an analytics dashboard and monitoring system to let us know when performance dips. This system has three components: a suite of benchmarks, a data warehouse for analytics, and an alerting system.&lt;/p&gt;
&lt;p&gt;&lt;img src="architecture.svg" alt="Performancing Monitoring Architecture"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benchmark Suite:&lt;/strong&gt; We&amp;rsquo;ve written a suite of benchmarks measuring different performance characteristics of the CLI. Each benchmark answers a simple question: &amp;ldquo;How long does the CLI take to run when the plan has no changes?&amp;rdquo; or &amp;ldquo;How long does the CLI take to run an empty plan?&amp;rdquo; In addition to these simple questions, we also have a few benchmarks to stress test creating many resources. We&amp;rsquo;ve implemented these benchmarks in Go, JavaScript, TypeScript, C#, and Python. Each night, we exercise the benchmarks, capturing samples. Once execution is complete, our nightly job uploads the results to a serverless function which stores them in our data warehouse.&lt;/p&gt;
&lt;p&gt;&lt;img src="analytics-dashboard.jpg" alt="A snippet of our analytics dashboard"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Warehouse Analytics:&lt;/strong&gt; We use Metabase to query and analyze data in our data warehouse. Pulumi engineers can log into Metabase and view our performance dashboard, which charts the nightly data as a time series, where each data point is the average of that night&amp;rsquo;s samples.&lt;/p&gt;
&lt;p&gt;Plotting the chart as a line graph allows us to identify any performance dips visually. Sometimes the data from our nightly runs are noisy, so it can take a few nights before we can attribute a change in the runtime to a change in the code. In the absence of noise, the line graph allows us to pinpoint the day the regression was introduced so we can leaf through the pull requests merged that day. More often, there&amp;rsquo;s enough noise in the data that we go through a few days of PRs to find the regression.&lt;/p&gt;
&lt;p&gt;Shortly after shipping the performance dashboard internally, we used the initial numbers we observed to establish a service-level objective (SLO).&lt;/p&gt;
&lt;p&gt;Each language has an SLO for each of the benchmarks implemented in that language. Our goal for the SLO is to ensure that we set a &amp;ldquo;do not breach&amp;rdquo; expectation across the Platform Core team. We want everyone on the team to know when performance slips beyond an acceptable level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alerting:&lt;/strong&gt; What good is an SLO if it&amp;rsquo;s not observable? Whether the breach comes from a major regression or creeps up over time, any time a benchmark violates an SLO, a Slackbot alerts the Platform Core team that performance has slowed beyond an acceptable level. One objective we had for the Amazing Performance initiative was to ensure there were no alerts by the end of Q3 2022, which we achieved.&lt;/p&gt;
&lt;p&gt;&lt;img src="metabot.jpg" alt="Screenshot of a Slack alert from a Metabase SLO breach"&gt;&lt;/p&gt;
&lt;p&gt;Lastly, we established the accuracy of these benchmarks as part of Amazing Performance. We compared the benchmark results we collected in the nightly job with numbers observed on our local laptops and found they were similar. Consequently, we believe our benchmarks are a good predictor of an actual developer&amp;rsquo;s experience.&lt;/p&gt;
&lt;h3 id="tracing-support"&gt;Tracing Support&lt;/h3&gt;
&lt;p&gt;If you ever encounter a particularly sluggish Pulumi program and want to know what is taking so long, you can use &lt;a href="https://www.pulumi.com/docs/support/troubleshooting/#tracing"&gt;Pulumi&amp;rsquo;s tracing support&lt;/a&gt; to look into the details. When run with the &lt;code&gt;--tracing&lt;/code&gt; flag, Pulumi will capture an &lt;a href="https://opentracing.io/"&gt;OpenTracing&lt;/a&gt; trace of the program&amp;rsquo;s execution, giving you an idea of where time is spent. We regularly trace programs large and small to see how we can shave off time.&lt;/p&gt;
&lt;p&gt;Pulumi can send traces to a Zipkin server, or aggregate them to a local file and let you view them offline. If you provide an HTTP URI to the &lt;code&gt;--tracing&lt;/code&gt; flag, then Pulumi assumes that URI points to a web server that accepts Zipkin-format traces, and it will send all tracing data there. If you provide a file URI instead, Pulumi spins up a local Zipkin server to which child processes send their traces. Once execution is complete, Pulumi aggregates the traces into a single local file and spins down the Zipkin server. You can run &lt;code&gt;PULUMI_DEBUG_COMMANDS=1 pulumi view-trace ./up.trace&lt;/code&gt; to view the trace in an embedded web UI.&lt;/p&gt;
&lt;p&gt;&lt;img src="appdash.jpg" alt="An image of a Pulumi trace"&gt;&lt;/p&gt;
&lt;p&gt;Not all spans are exceptionally well-named, and there are blind spots in our traces. However, traces are typically a reliable way to subdivide the program execution, so hotspots are easily identifiable. In the future, we plan to migrate to OpenTelemetry and improve our trace coverage. (Feel free to get in touch on the &lt;a href="https://slack.pulumi.com/"&gt;Community Slack&lt;/a&gt; if you want to help with this effort!)&lt;/p&gt;
&lt;h2 id="quick-wins-for-performance-gains"&gt;Quick Wins for Performance Gains&lt;/h2&gt;
&lt;p&gt;Some of the work we completed for Amazing Performance targeted simple, isolated changes to make the system a little snappier. The rest of this blog details two quick examples.&lt;/p&gt;
&lt;h3 id="skipping-a-slow-import"&gt;Skipping a Slow Import&lt;/h3&gt;
&lt;p&gt;We cut 300ms in boot time a class of programs by dynamically importing TypeScript.&lt;/p&gt;
&lt;p&gt;When Pulumi evaluates NodeJS programs, it compiles TypeScript into JavaScript using &lt;a href="https://www.npmjs.com/package/ts-node"&gt;TS-Node&lt;/a&gt;. Consequently, Pulumi bundles the TypeScript compiler into its NodeJS runtime so TS-Node can perform the compilation. However, we noticed JavaScript programs were importing the TypeScript compiler even when unused.&lt;/p&gt;
&lt;p&gt;Users typically hit this bug in one of two ways. Some users choose to write Pulumi programs in pure JavaScript. Pulumi was needlessly importing the TypeScript compiler, even when there was nothing to compile.&lt;/p&gt;
&lt;p&gt;The second way a user might encounter this bug is in a more advanced deployment pipeline. One common advanced pattern in CI environments or with &lt;a href="https://www.pulumi.com/docs/using-pulumi/automation-api/"&gt;Automation API&lt;/a&gt; is to typecheck Pulumi programs separately from execution. CI will enforce typechecking during pull requests to guarantee that all code hitting the repository&amp;rsquo;s main branch is valid. Once it&amp;rsquo;s typechecked and merged, the Pulumi program can precompile the TypeScript into JavaScript before execution. Finally, when it comes time to execute the Pulumi program, the deployment pipeline will run the compiled JavaScript, which is known to be well-typed. The motivation for precompiling comes from using your own preprocessor. Some customers want to ditch TS-Node for compilation and prefer to use &lt;a href="https://swc.rs/"&gt;SWC&lt;/a&gt;, &lt;a href="https://esbuild.github.io/"&gt;ESBuild&lt;/a&gt;, or other options for increased performance, or for consistency with the rest of their codebase.&lt;/p&gt;
&lt;p&gt;In both scenarios, importing TypeScript when it&amp;rsquo;s not used results in a 300ms slowdown. In &lt;a href="https://github.com/pulumi/pulumi/pull/10214"&gt;a small PR&lt;/a&gt;, we detect cases that don&amp;rsquo;t require TypeScript and dynamically import it only when needed.&lt;/p&gt;
&lt;h3 id="timely-lease-renewal"&gt;Timely Lease Renewal&lt;/h3&gt;
&lt;p&gt;Another quick win involved the renewal of a new lease, which resulted in another 120ms shaved off of boot time. When the Pulumi CLI kicks off a preview, projects that use the &lt;a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/#pulumi-service-backend"&gt;Service backend&lt;/a&gt; are authenticated with the Service so the CLI can fetch the current state. During authentication, the CLI exchanges user credentials for a short-lived access token. This access token is valid for a fixed amount of time, called a lease, after which they expire. The CLI can renew a lease by making another request to the Service to extend the duration for which the lease is valid.&lt;/p&gt;
&lt;p&gt;We noticed that the first time the CLI renewed the lease, it blocked further execution until the renewal was complete, introducing an unnecessary slowdown of 120ms. &lt;a href="https://github.com/pulumi/pulumi/pull/10462"&gt;The fix&lt;/a&gt; was to remove this renewal from the critical path by running it in the backend. By starting the HTTP call on a background thread and only blocking if the token is needed but not renewed, we&amp;rsquo;ve eliminated the 120ms slowdown.&lt;/p&gt;
&lt;p&gt;Since this bug affects anyone using the Pulumi Service backend, most users will benefit from this improvement. It&amp;rsquo;s small, &lt;a href="https://link.springer.com/chapter/10.1007/978-3-319-58475-1_4"&gt;but it&amp;rsquo;s noticeable&lt;/a&gt; to most users. Program start-up will feel slightly more snappy.&lt;/p&gt;
&lt;h2 id="stay-tuned"&gt;Stay Tuned!&lt;/h2&gt;
&lt;p&gt;This post covered tools we built before Amazing Performance and a few smaller changes we made as part of the initiative. Future posts will describe some of the larger advances we made to drive Pulumi performance ever forward! Keep an eye out for our next post in the series!&lt;/p&gt;</description><author>Robbie McKinstry</author><category>performance</category><category>platform</category><category>engineering</category></item><item><title>Enabling Rapid Pulumi Prototyping with Rust</title><link>https://www.pulumi.com/blog/pulumi-watch-mode-with-rust/</link><pubDate>Wed, 21 Sep 2022 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/pulumi-watch-mode-with-rust/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/pulumi-watch-mode-with-rust/index.png" /&gt;
&lt;p&gt;Pulumi enables engineers to employ the best practices of their field to &lt;a href="https://www.pulumi.com/what-is/what-is-infrastructure-as-code/"&gt;infrastructure as code&lt;/a&gt;. The
&lt;code&gt;pulumi watch&lt;/code&gt; command is an example of this, enabling rapid prototyping and a &amp;ldquo;hot reload&amp;rdquo; style
developer experience for prototyping Pulumi programs. In this post you&amp;rsquo;ll see what watch mode
enables, the challenges encountered in maintaining the feature, and how we were able to use Rust to
bring that feature to more of our users.&lt;/p&gt;
&lt;p&gt;When developing a web application, hot reload frameworks enable quickly iterating and confirming the
divs are centered. When implementing new features, a test runner with a watch mode makes test-driven
development a breeze. In languages with an interactive shell, it&amp;rsquo;s natural to use that to experiment
with new APIs. In each of these cases, engineers find value in being able to focus on writing code,
not typing console commands.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s why &lt;a href="https://www.pulumi.com/blog/pulumi-watch-mode-fast-inner-loop-development-for-cloud-infrastructure/"&gt;Pulumi Watch Mode&lt;/a&gt;
exists and is one of my favorite features, and it&amp;rsquo;s why I was disappointed to learn this feature was
not available to users on Apple Silicon Macs. As a Windows and Linux user, I discovered this while
root causing a Pulumi provider&amp;rsquo;s failing build to the library used to implement watch mode. Thus
began a deep dive into cgo, file-watching libraries in Go, operating system event APIs, and finding
a solution for our build issues and missing feature.&lt;/p&gt;
&lt;p&gt;To close this feature gap for one of our most important platforms, Pulumi v3.39.0 includes a binary
built with Rust to implement file-watching. &lt;code&gt;pulumi watch&lt;/code&gt; works as it
always has. And as the first component to use the Rust language, it validated Pulumi&amp;rsquo;s approach to
engineering of using the best languages and tools available.&lt;/p&gt;
&lt;h2 id="but-first-what-is-and-why-use-watch-mode"&gt;But first, what is and why use watch mode?&lt;/h2&gt;
&lt;p&gt;Watch mode monitors a project folder and automatically runs an equivalent to &lt;code&gt;pulumi up&lt;/code&gt; with the
appropriate flags to skip prompts and displays simplified output. To see how quickly this can work,
I wrote a Pulumi TypeScript program to deploy an S3 bucket and bucket object &amp;ldquo;test.txt&amp;rdquo; before
writing a timestamp to the same file. That allows me to measure the inner loop with &lt;code&gt;pulumi watch&lt;/code&gt;
or using an external program to run &lt;code&gt;pulumi up&lt;/code&gt;. On each deployment, the program changes one of the
files in the directory kicking off the next deployment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/pulumi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/aws&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fs/promises&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucketObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BucketObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-bucket-object&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;: &lt;span class="kt"&gt;bucket.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;source&lt;/span&gt;: &lt;span class="kt"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FileAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;test.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;bucketObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="sb"&gt;n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On a laptop with this simple program, &lt;code&gt;pulumi watch&lt;/code&gt; consistently completed up to 50% faster, at 2.1
seconds per iteration than when using an external tool. Watch mode was more consistent, too: the
standard deviation over 50 samples was reduced to 46ms from 216ms. It does this while being easier
to remember and therefore use correctly, and with more terse output tuned for the use case of
quickly iterating on changes to a stack.&lt;/p&gt;
&lt;p&gt;Watch mode has changed how I write infrastructure as code, and it brings that benefit to everyone
without needing to install and learn third-party tools for file-watching.&lt;/p&gt;
&lt;h2 id="the-challenges-of-file-watching-in-go"&gt;The Challenges of File-Watching in Go&lt;/h2&gt;
&lt;p&gt;Returning to the cross-compilation issue, the team found builds failed after updating
the Pulumi library dependency. Root causing this, I found that through a chain of Go package
dependencies, the file-watching library became a compilation unit in provider binaries. That library
had several reported issues with cross-compilation, with users reporting that it required enabling
cgo and building on macOS to target macOS.&lt;/p&gt;
&lt;p&gt;That raised two flags. First, &lt;a href="https://dave.cheney.net/2016/01/18/cgo-is-not-go"&gt;cgo is not Go&lt;/a&gt;.
Using cgo entails wide-ranging side effects and results in a more complex build, packaging, and
support lifecycle. Second, continuous integration tools that support macOS runners are limited and
typically much higher cost. It was unacceptable to allow unintentional changes in a provider or the
Pulumi repository to impose those requirements on providers.&lt;/p&gt;
&lt;p&gt;Why did the &lt;a href="https://github.com/rjeczalik/notify"&gt;notify library&lt;/a&gt; have these constraints? This
library depended on C library support for a macOS API called FSEvents, and its Go source files
required a complex set of build tags. While this didn&amp;rsquo;t seem impossible to work around, the library
remains unmaintained and would require us to maintain a fork to address these deficiencies. With no
recent contributions to the library to address this or a path to removing the cgo dependency, I had
to find another library.&lt;/p&gt;
&lt;p&gt;The strongest candidate was the &lt;a href="https://github.com/fsnotify/fsnotify"&gt;fsnotify library&lt;/a&gt;. This library supported
the platforms required, did not require cgo, and supported cross-compilation. It did lack recursive
directory watching, though the repository listed it as a planned feature. However, in August the
maintainer archived the repository and stepped down from maintenance. Though contributors stepped up
to take over ownership of the repo, it left the state of the project in doubt. Without features like
recursive file watching, the team would have to maintain a fork. The search continued.&lt;/p&gt;
&lt;p&gt;The last candidate looked at was the &lt;a href="https://github.com/radovskyb/watcher/"&gt;watcher library&lt;/a&gt;, which supported
recursive file watching across all platforms without cgo. Was this a panacea? Unfortunately, no. It
did this by falling back to the simplest method of file-watching: polling. This method works well
enough that the library has not changed in four years. The downside to polling is substantially
higher CPU usage and battery drain. It would also require workarounds for projects that contain
large folders of dependencies, such as a node.js node_modules or python venv directory. In spiking
on this, I was unsurprised to see that watching a folder with vendored dependencies was orders of
magnitude slower.&lt;/p&gt;
&lt;p&gt;The Go language ecosystem lacks libraries that meet our needs. It was possible to watch only one
directory, or only support one operating system, or to be insensitive to performance concerns. I
needed an option that didn&amp;rsquo;t make any of these sacrifices and, ideally, was also actively
maintained.&lt;/p&gt;
&lt;h2 id="from-library-to-binary-and-the-carcinization-of-pulumi"&gt;From library to binary and the carcinization of Pulumi&lt;/h2&gt;
&lt;p&gt;One doesn&amp;rsquo;t have to look hard to find CLIs that tackle this problem, many of which were at the top
of search results while looking for libraries. Using one of these as a plugin and reading stdout for
events instead would avoid any link-time dependency for providers. With this in mind, I considered
building a one-off binary using the previous utilities but ruled them out. The team would become de
facto maintainers of a fork of a complex library, in addition to needing to solve the deficiencies
identified above. That wasn&amp;rsquo;t an innovation token I could spend.&lt;/p&gt;
&lt;p&gt;I evaluated the complexity of building on the &lt;a href="https://github.com/facebook/watchman"&gt;watchman service&lt;/a&gt;, an
excellent file-watching CLI open-sourced by Facebook. Unfortunately, it would be costly to maintain
a wrapper with watchman as a dependency. While C++ isn&amp;rsquo;t inaccessible, it does impose a complex
build system and a higher barrier to entry for engineers to contribute to it. And as both a client
and server application, it wouldn&amp;rsquo;t be as simple as packaging a single binary and shelling out to
it.&lt;/p&gt;
&lt;p&gt;Finally, I spiked on a library written in Rust, using the library underpinning the
&lt;a href="https://github.com/watchexec/watchexec"&gt;watchexec CLI&lt;/a&gt;. All-in, this worked out to just over 100
lines of code to maintain on top of the actively maintained watchexec libraries, with a similarly
small patch to the Pulumi CLI. And importantly, building the binary is a simple &lt;code&gt;cargo build&lt;/code&gt; command that
the team can run on any platform. The
&lt;a href="https://github.com/pulumi/watchutil-rs"&gt;watchutil-rs implementation&lt;/a&gt; lives in GitHub and is packaged as
pulumi-watch, a single-purpose binary that logs on stdout when files change.&lt;/p&gt;
&lt;p&gt;Pulumi&amp;rsquo;s mission is to enable everyone to apply the best programming languages and tools to
infrastructure as code. Implementing watch mode in Rust applied that principle to watch mode while
maintaining performance, moving the feature to a well-maintained footing, and expanding support to
every platform Pulumi supports.&lt;/p&gt;
&lt;p&gt;Give &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_watch"&gt;&lt;code&gt;pulumi watch&lt;/code&gt;&lt;/a&gt; a try with our
&lt;a href="https://www.pulumi.com/docs/get-started/"&gt;getting started guide&lt;/a&gt;!&lt;/p&gt;</description><author>Aaron Friel</author><category>features</category><category>watch</category><category>rust</category></item><item><title>Node.js Native Binary Compilation Using vercel/pkg</title><link>https://www.pulumi.com/blog/nodejs-binaries-with-pkg/</link><pubDate>Tue, 13 Sep 2022 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/nodejs-binaries-with-pkg/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/nodejs-binaries-with-pkg/index.png" /&gt;
&lt;p&gt;In Pulumi&amp;rsquo;s engineering department, we often build and distribute tools as native binaries to avoid the need for additional dependencies on user machines. Most of these tools are written in Go, which has good support for building self-contained binaries that target modern operating systems.&lt;/p&gt;
&lt;p&gt;While other Pulumi-supported languages like Node.js, Python, and .NET require additional runtime dependencies, it&amp;rsquo;s possible to bundle dependencies with the program. In this article, we&amp;rsquo;ll show you how to do that for a Node.js program.&lt;/p&gt;
&lt;p&gt;This is a problem that can be solved using &lt;a href="https://github.com/vercel/pkg"&gt;vercel/pkg&lt;/a&gt; command line tool. Here&amp;rsquo;s the summary from their readme.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This command line interface enables you to package your Node.js project into an executable that can be run even on devices without Node.js installed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at how to use &lt;code&gt;pkg&lt;/code&gt; and some issues we encountered on the way.&lt;/p&gt;
&lt;h2 id="setting-up-pkg"&gt;Setting up &lt;code&gt;pkg&lt;/code&gt;&lt;/h2&gt;
&lt;h3 id="1---install"&gt;1 - Install&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pkg&lt;/code&gt; is distributed as an npm package which can be installed into your “devDependencies” using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -D pkg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yarn add -D pkg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or run without installing with npx:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx pkg &lt;span class="o"&gt;[&lt;/span&gt;args&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2---set-bin-in-packagejson"&gt;2 - Set &lt;code&gt;bin&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pkg&lt;/code&gt; will use &lt;a href="https://docs.npmjs.com/cli/v6/configuring-npm/package-json#bin"&gt;the bin field from your package.json&lt;/a&gt; to find the entry point so you just have to specify a path to the directory containing your &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my-program&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bin/index.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If no target is specified, then a set of defaults will be chosen for you. If the output path is not specified, &lt;code&gt;pkg&lt;/code&gt; will infer the name from the package.json “name” field and write to the current working directory.&lt;/p&gt;
&lt;h3 id="3---execute"&gt;3 - Execute&lt;/h3&gt;
&lt;p&gt;The main inputs that &lt;code&gt;pkg&lt;/code&gt; needs is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The entry point to your program for packaging&lt;/li&gt;
&lt;li&gt;The target machine to build for&lt;/li&gt;
&lt;li&gt;The output path to write the finished binary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s an example of building the project in the current directory using node v18 for macOS ARM architectures:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# pkg [options] &amp;lt;input&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# -t, --targets comma-separated list of targets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# -o, --output output file name or template for several files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg -t node18-macos-arm64 -o bin/my-program .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s possible to specify multiple targets in a comma-separated list to build them all at the same time, but it does come with the limitation where the output file names follow a fixed pattern. Instead, we chose to just run the &lt;code&gt;pkg&lt;/code&gt; command multiple times with different arguments from our makefile in parallel as this fits with our existing workflows well.&lt;/p&gt;
&lt;h2 id="issues-encountered"&gt;Issues encountered&lt;/h2&gt;
&lt;h3 id="inspector-not-available"&gt;“Inspector Not Available”&lt;/h3&gt;
&lt;p&gt;As soon as we started executing the provider we started seeing some interesting warnings printed to the console stating “Inspector is not available” (here’s our &lt;a href="https://github.com/pulumi/pulumi-awsx/issues/848"&gt;tracking issue&lt;/a&gt; and some &lt;a href="https://github.com/vercel/pkg/issues/93"&gt;&lt;code&gt;pkg&lt;/code&gt; discussion&lt;/a&gt;). This is because we use the Node.js Inspector API as part of our automatic closure serialization, however this is not available by default when packaging with &lt;code&gt;pkg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pkg&lt;/code&gt; provides an option to fix this by building your own base image with custom Node.js flags set to enable debugging. However, on investigation, these issues were caused by the Pulumi Typescript SDK creating and caching an inspector instance at the point of being imported even though we never actually call this code in our plugin. Therefore, we opted to make this eager singleton creation to be lazy – only created on first use, as we’re not using in our providers at this time.&lt;/p&gt;
&lt;h3 id="unrunnable-macos-arm-binaries"&gt;Unrunnable MacOS ARM binaries&lt;/h3&gt;
&lt;p&gt;When trying to use the binaries produced by our CI, we found that &lt;a href="https://github.com/pulumi/pulumi-awsx/issues/850"&gt;the binaries weren’t runnable on MacOS ARM architectures&lt;/a&gt; - and were forcibly killed by the operating system.&lt;/p&gt;
&lt;p&gt;This led me down quite a bit of a rabbit hole investigating signing of binaries, but it was actually resolved by simply installing the ‘ldid’ tool on our linux CI environments. The ldid source is available via &lt;a href="git://git.saurik.com/ldid.git"&gt;git.saurik.com/ldid&lt;/a&gt; and binaries are available from various sources. Our solution was to use the &lt;a href="https://github.com/marketplace/actions/install-ldid"&gt;“Install Ldid” GitHub Action&lt;/a&gt; to install the ldid binary and add it to the PATH in our CI workflow. My learning here is that sometimes reading the warnings in logs more carefully can save you lots of time!&lt;/p&gt;
&lt;h3 id="static-binaries"&gt;Static Binaries&lt;/h3&gt;
&lt;p&gt;One adjustment to our configuration came from a &lt;a href="https://github.com/pulumi/pulumi-awsx/pull/862"&gt;community contribution by @afreakk&lt;/a&gt; where the provider was being used in a nixos environment. Nixos adds the requirement for all binaries to be static rather than dynamic - so there’s no requirement for the operating system to dynamically map link functions from system libraries at runtime. Statically compiled programs sometimes result in a larger size, but avoid any possible issues with different versions of the libraries it depends on.&lt;/p&gt;
&lt;p&gt;To resolve this issue, it’s as simple as changing the ‘linux’ targets to ‘linuxstatic’. E.g. &lt;code&gt;node18-linux-amd64&lt;/code&gt; becomes &lt;code&gt;node18-linuxstatic-amd64&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="multi-platform-builds-with-a-makefile"&gt;Multi-platform builds with a makefile&lt;/h2&gt;
&lt;p&gt;We use makefiles to build our providers, so here&amp;rsquo;s a brief outline of how we build for multiple platforms using GNU Make.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-make" data-lang="make"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Set the correct pkg TARGET for each binary we build
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# when building for linux-amd64, set the pkg target to node18-linuxstatic-x64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bin/linux-amd64/my-program&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; := &lt;span class="n"&gt;node&lt;/span&gt;18-&lt;span class="n"&gt;linuxstatic&lt;/span&gt;-&lt;span class="n"&gt;x&lt;/span&gt;64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# output binary file ^ ^ variable ^ pkg target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bin/linux-arm64/my-program&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; := &lt;span class="n"&gt;node&lt;/span&gt;18-&lt;span class="n"&gt;linuxstatic&lt;/span&gt;-&lt;span class="n"&gt;arm&lt;/span&gt;64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bin/darwin-amd64/my-program&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; := &lt;span class="n"&gt;node&lt;/span&gt;18-&lt;span class="n"&gt;macos&lt;/span&gt;-&lt;span class="n"&gt;x&lt;/span&gt;64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bin/darwin-arm64/my-program&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; := &lt;span class="n"&gt;node&lt;/span&gt;18-&lt;span class="n"&gt;macos&lt;/span&gt;-&lt;span class="n"&gt;arm&lt;/span&gt;64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bin/windows-amd64/my-program.exe&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; := &lt;span class="n"&gt;node&lt;/span&gt;18-&lt;span class="n"&gt;win&lt;/span&gt;-&lt;span class="n"&gt;x&lt;/span&gt;64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Wildcard rule to build any of binary outputs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# &amp;#34;To build any bin file, ensure node_modules are up to date...&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bin/%&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;node_modules&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# &amp;#34;... then run pkg for actual output name &amp;amp; target&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; yarn run pkg . --target &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; --output &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# &amp;#34;TARGET&amp;#34; is the variable defined above, depending on the output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# &amp;#34;$@&amp;#34; is the current makefile target - e.g. &amp;#39;bin/linux-amd64/my-program&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Running `make bins` will build all the listed outputs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;linux&lt;/span&gt;-&lt;span class="n"&gt;amd&lt;/span&gt;64/&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;linux&lt;/span&gt;-&lt;span class="n"&gt;arm&lt;/span&gt;64/&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;darwin&lt;/span&gt;-&lt;span class="n"&gt;amd&lt;/span&gt;64/&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;darwin&lt;/span&gt;-&lt;span class="n"&gt;arm&lt;/span&gt;64/&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;windows&lt;/span&gt;-&lt;span class="n"&gt;amd&lt;/span&gt;64/&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;program&lt;/span&gt;.&lt;span class="n"&gt;exe&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Let Make know that `bins` doesn&amp;#39;t really exist (is phony) - it&amp;#39;s just a helpful shortcut
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In summary the above code does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define each output file we need
&lt;ul&gt;
&lt;li&gt;Set the correct &lt;code&gt;pkg&lt;/code&gt; &lt;code&gt;TARGET&lt;/code&gt; for each output&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Define a rule for building any binary output:
&lt;ul&gt;
&lt;li&gt;Ensure module_modules are up-to-date (this is another make target not shown here).&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;pkg&lt;/code&gt; with the &lt;code&gt;$TARGET&lt;/code&gt; set in (1) and the name of the current output (&lt;code&gt;$@&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Define &lt;code&gt;bins&lt;/code&gt; &amp;ldquo;phony target&amp;rdquo; which builds all listed bins.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;
&lt;p&gt;&lt;strong&gt;You might also like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/benchmarking-python-performance/"&gt;
Benchmarking Python Performance
&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/blog/amazing-performance/"&gt;
Achieving Amazing Performance in the Pulumi CLI
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;I hope this gives you a good overview of the vercel/pkg tool and how you can use it to create standalone programs using Node.js.&lt;/p&gt;
&lt;h3 id="historical-note"&gt;Historical Note&lt;/h3&gt;
&lt;p&gt;We used to do a similar process using the &lt;a href="https://github.com/nexe/nexe"&gt;nexe project&lt;/a&gt;, but there’s been no releases since 2017 and therefore no support for newer versions of Node.js and we therefore consider this package as unmaintained at this point in time.&lt;/p&gt;</description><author>Daniel Bradley</author><category>engineering</category><category>nodejs</category><category>publishing</category><category>providers</category></item><item><title>Exploring how to solve circular dependencies</title><link>https://www.pulumi.com/blog/exploring-circular-dependencies/</link><pubDate>Wed, 12 Jan 2022 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/exploring-circular-dependencies/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/exploring-circular-dependencies/index.png" /&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.74fadd1b94bae866bccf29a780f184a71c5cfc34c8677be70da8fe2ab0309b9e.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;This blog post originated from a hackathon and exploratory exercise, and should be considered in that context. None of the solutions exist in the Pulumi SDKs. For more information on this, please view our &lt;a href="https://www.pulumi.com/blog/exploring-circular-dependencies/"&gt;blog post on Circular Dependencies&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;As part of our hackathon near the end of last year, we decided to explore solutions to a common problem when people are using Pulumi for their systems. A question that’s been asked in a few different forms is how to resolve circular dependencies between resources in a Pulumi program.&lt;/p&gt;
&lt;p&gt;A simple example of this idea is a modern web application with a static front-end and an API, where the front-end needs to know the URL of the API to be able to call it and the API needs to know the source domain of the front-end to allow it access via CORS. As these two resources rely on one another to be created, they are circular dependencies.&lt;/p&gt;
&lt;p&gt;&lt;img src="intro.png" alt="Image showing a JS app passing its domain to an API and the API passing its domain to the JS app"&gt;&lt;/p&gt;
&lt;p&gt;For a deployable example, let’s build a serverless game of ping-pong—two functions that invoke one another until one misses!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ping&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;pong.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Can&amp;#39;t access `pong` yet!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;ping.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Programming languages don’t let us create one object that takes a value from another object that doesn’t yet exist. Therefore, we can’t express a circular dependency in this way.&lt;/p&gt;
&lt;p&gt;The simplest workaround for this error is to get a reference to our own stack, which contains the outputs from the previous deployment. Now, we set the reference to &lt;code&gt;pong&lt;/code&gt; within &lt;code&gt;ping&lt;/code&gt; from the output of the last deployment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentStack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`acme-org/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getProject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getStack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ping&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;currentStack&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pongName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;pongName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pongName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;ping.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pingName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pongName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The downside of this approach is that we have to deploy twice to reach the desired state because we need the results of the previous deployment, though this approach might be fine for simple one-off circumstances.&lt;/p&gt;
&lt;p&gt;How then could we make the process easier and clearer for users who need to resolve this kind of circular dependency regularly? We explored four options as part of the hackathon, so let’s go through each one, getting progressively closer to a possible solution.&lt;/p&gt;
&lt;h2 id="option-1-state"&gt;Option 1: State&lt;/h2&gt;
&lt;p&gt;Instead of referencing outputs directly from our stack’s previous deploy and manually deploying twice, we could introduce a special type called “State”.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;State&lt;/code&gt; object does 3 tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the value from the previous deployment, or provide a default if it’s the first deployment.&lt;/li&gt;
&lt;li&gt;Update the value in the stack state using a “set” method.&lt;/li&gt;
&lt;li&gt;Automatically triggers another deployment if its value has changed.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pongName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong-name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ping&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;pongName.value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;ping.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Engine re-runs &amp;#34;up&amp;#34; if this changes the value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;pongName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first deployment will match the preview, but when triggering the re-deployment, we’re making a change that wasn’t in the first preview. This action could also result in a never-ending loop where every deployment makes another change to the state and triggers another deployment. So let’s explore a different option that might not have this issue.&lt;/p&gt;
&lt;h2 id="option-2-refs"&gt;Option 2: Refs&lt;/h2&gt;
&lt;p&gt;This option looks almost identical to the “state” concept above, except we’re calling them references (refs):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pongName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong-name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ping&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;pongName.value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Special type the Pulumi engine will track
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;ping.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Determine affected resources and re-update
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;pongName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The difference between refs and state is that, instead of triggering a whole re-deploy when the value changes, refs would be integrated into the dependency graph similarly to resources. When the value of the ref changes, we could internally work out which resources depend on that ref and change the target state for only those resources. This avoids the possibility of a never-ending update loop.&lt;/p&gt;
&lt;p&gt;Both the “state” and “refs” approaches have the downside of not making the final outputs of the updated resource available to the rest of the program. The resource object would still have the values from after it first deployed. This means that other parts of the program might still deploy differently on subsequent deployments. Let’s explore another option that might address that problem.&lt;/p&gt;
&lt;h2 id="option-3-create-patch-resources"&gt;Option 3: Create patch resources&lt;/h2&gt;
&lt;p&gt;We could explicitly model the third step by using a new resource type suffixed with “patch”:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ping&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;ping.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pingPatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunctionPatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ping-patch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;ping.id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;pong.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The provider presents pseudo-resources for update or patch operations. The &lt;code&gt;patch&lt;/code&gt; resource is using the same underlying resource as the original resource. This option is a very flexible approach that could be implemented by providers right now. These patch resources could also be used for resources that exist outside the Pulumi stack. The drawback is that the &lt;code&gt;patch&lt;/code&gt; operation has to be implemented in each provider individually, and it significantly increases the overall size of each provider and its associated SDK. We’re still evaluating whether this option would be a good one considering that size increase, which increases download times as an example impact, and the engineering resources to implement the change. We have one more option to consider, as well.&lt;/p&gt;
&lt;h2 id="option-4-allow-a-resource-to-update-its-goal-state"&gt;Option 4: Allow a resource to update its goal state&lt;/h2&gt;
&lt;p&gt;When using the SDK, we could write something like the following code to create then update a little later on:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ping&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CallbackFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;: &lt;span class="kt"&gt;pingPongHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;ping.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedPing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;OPPONENT_FN_NAME&lt;/span&gt;: &lt;span class="kt"&gt;pong.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When declaring a resource in a Pulumi program, you’re essentially saying, “This is the state I want my resource to be in.” The update method returns a whole new copy of the resource with new properties containing the outputs of the update.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;The final option is quite close to a fully working solution, but there still remains a few tasks to complete before we could release it as a feature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finalise what the internal messages between the Pulumi’s internal components should look like.&lt;/li&gt;
&lt;li&gt;Keep a record of circular dependencies in the stack state without causing knock-on issues elsewhere in Pulumi’s internal workings.&lt;/li&gt;
&lt;li&gt;Decide what should happen if an update triggers a replacement—if this is potentially valid behaviour or not. We should be able to generate the set of properties which are able to be updated without causing replacement and provide these properties to the user as the arguments for the update method to avoid these issues before execution of the program.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some other ideas that would be interesting to explore in the future are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How could we utilise patching of resources where a provider can support it?&lt;/li&gt;
&lt;li&gt;Could we delete a resource that was created earlier in the program to allow the creation of temporary resources?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This hackathon project has opened up a number of new pathways for us to explore. If you want to participate, we always welcome contributions in our open-source repos at &lt;a href="https://github.com/pulumi"&gt;https://github.com/pulumi&lt;/a&gt;. We hope you have enjoyed this dive into how our team explored this concept as part of the hackathon. We’ll be exploring more of the hackathon projects soon on the blog!&lt;/p&gt;</description><author>Daniel Bradley</author><author>Fraser Waters</author><author>Mikhail Shilkov</author><category>features</category><category>hackathon</category></item><item><title>Supercharging our GitHub Action with the Automation API</title><link>https://www.pulumi.com/blog/supercharging-our-github-action-with-the-pulumi-automation-api/</link><pubDate>Mon, 01 Mar 2021 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/supercharging-our-github-action-with-the-pulumi-automation-api/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/supercharging-our-github-action-with-the-pulumi-automation-api/index.png" /&gt;
&lt;p&gt;&lt;strong&gt;Guest Article:&lt;/strong&gt; &lt;a href="https://github.com/cobraz"&gt;Simen A. W. Olsen&lt;/a&gt; is a Software Architect and Manager at &lt;a href="https://bjerk.io"&gt;Bjerk&lt;/a&gt;,
a software development agency based in Oslo, Norway. He joins Paul Stack to talk about the new GitHub Action powered by
the Pulumi Automation API.&lt;/p&gt;
&lt;p&gt;In this article, we&amp;rsquo;ll take a look at how our &lt;a href="https://github.com/pulumi/actions"&gt;GitHub Action&lt;/a&gt; has been re-architected
to be built on top of our &lt;a href="https://www.pulumi.com/blog/automation-api/"&gt;Automation API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a GitHub Actions launch partner in 2018, we released our original GitHub Action, built as a &lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action"&gt;Docker container action&lt;/a&gt;.
This Action packaged all the prerequisites with the Pulumi CLI to let users
run Pulumi commands. As the Pulumi ecosystem has grown, so too did the container run in GitHub Actions.&lt;/p&gt;
&lt;p&gt;Since then, GitHub released an alternative way to create a GitHub Action
&lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action"&gt;by writing JavaScript&lt;/a&gt;.
At Pulumi, and in our community, we love expressing everything we possibly can in code, so the new JavaScript support got us quite
excited about the &amp;ldquo;better together&amp;rdquo; possibilities! &lt;a href="https://www.pulumi.com/blog/automation-api"&gt;Enter Pulumi&amp;rsquo;s new Automation API&lt;/a&gt;,
a new feature we launched in public preview in October 2020. This new capability lets you build infrastructure as code—in
pure code—without even needing to run a CLI. This sounded like a perfect match for a JavaScript Action.
Creating a JavaScript Action allows us to programmatically invoke the right Pulumi commands in response to actions, without needing to awkwardly
script a CLI. Instead, we were able to leverage the Automation API to fundamentally rethink our GitHub Action.&lt;/p&gt;
&lt;h3 id="under-the-hood"&gt;Under the Hood&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at the new JavaScript-based GitHub Action and see how it uses Automation API to execute Pulumi:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;makeConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environmentVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PULUMI_ACCESS_TOKEN&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pulumiCli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pulumiCli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environmentVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_WORKSPACE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;LocalWorkspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectStack&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;stackName&lt;/span&gt;: &lt;span class="kt"&gt;config.stackName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;workDir&lt;/span&gt;: &lt;span class="kt"&gt;workDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.....&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;: &lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;()&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onOutput&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onOutput&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onOutput&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;preview&lt;/span&gt;: &lt;span class="kt"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;]();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;outKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outExport&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outExport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outExport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outExport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Action code is clean and concise because the power lies in the Automation API itself. The Action uses the Automation
API to drive Pulumi as part of your GitHub Actions workflows. This means that your workflows can take advantage of changes to Pulumi
and the Automation API as soon as they&amp;rsquo;re released! And, of course, you can always write your own custom logic
using the Automation API like our Action does.&lt;/p&gt;
&lt;h3 id="customizing-your-environment"&gt;Customizing Your Environment&lt;/h3&gt;
&lt;p&gt;The new Action puts you in control of the environment that it runs on. By comparison, the previous Docker-based
Action bundled specific tool versions, which left your CI pipeline at the mercy of our opinionated container. Now, if
you want to pin to specific versions of NodeJS / Python / Go / .NET or even Pulumi, you have that power!&lt;/p&gt;
&lt;p&gt;You can even include a matrix strategy to test your Pulumi code against different language runtime versions and
different operating systems:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Sample Pulumi NodeJS Application&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test-workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.os }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Testing Pulumi Preview on ${{ matrix.os }} with NodeJS ${{ matrix.node_version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;os&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest, macos-latest, windows-latest]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;node_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;10.&lt;/span&gt;&lt;span class="l"&gt;x, 12.x, 14.x]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;node-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.node_version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install pulumi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pulumi/action-install-pulumi-cli@v1.0.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;npm install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pulumi/actions@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PULUMI_CONFIG_PASSPHRASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;not-a-secret&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;preview&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cloud-url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;file://~&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="cloud-interaction"&gt;Cloud Interaction&lt;/h3&gt;
&lt;p&gt;The previous Docker-based Action would look for the &lt;code&gt;GOOGLE_CREDENTIALS&lt;/code&gt; environment
variable; if your workflow set that variable, the Action would run the following commands on your behalf:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gcloud auth activate-service-account --key-file&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gcloud --quiet auth configure-docker &lt;span class="nv"&gt;$GOOGLE_DOCKER_HOSTNAME_LIST&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With the new Action, you&amp;rsquo;re fully in control of authentication to cloud providers. To authenticate
with AWS / Azure / Google Cloud, you can now use their official Actions:&lt;/p&gt;
&lt;div&gt;
&lt;pulumi-chooser type="cloud" options="aws,azure,gcp" mode=""&gt;&lt;/pulumi-chooser&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="cloud" values="aws" mode=""&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Configure AWS Credentials&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;aws-region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AWS_REGION }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="cloud" values="azure" mode=""&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azure/login@v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_CREDENTIALS }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pulumi-choosable type="cloud" values="gcp" mode=""&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;google-github-actions/setup-gcloud@v0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service_account_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.GCP_KEY }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ env.PROJECT_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;export_default_credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/pulumi-choosable&gt;
&lt;/div&gt;
&lt;p&gt;We recommend that you store all sensitive environment variables, like cloud credentials, in the
Actions &lt;a href="https://docs.github.com/en/actions/reference/encrypted-secrets/"&gt;secret storage&lt;/a&gt;
and consume them using
&lt;a href="https://docs.github.com/en/actions/reference/encrypted-secrets#using-encrypted-secrets-in-a-workflow"&gt;the &lt;code&gt;secrets&lt;/code&gt; attribute&lt;/a&gt;
on your workflow&amp;rsquo;s action.&lt;/p&gt;
&lt;h3 id="faster"&gt;Faster&lt;/h3&gt;
&lt;p&gt;The new GitHub Action is much smaller and no longer needs to download a (very!) large Docker container. In the Docker-based
Action, we frequently saw workflows take up to 3 minutes to download the container. The new Action is much faster;
let&amp;rsquo;s take a look:&lt;/p&gt;
&lt;h4 id="old-docker-based-action-v1"&gt;Old Docker-based Action (v1)&lt;/h4&gt;
&lt;p&gt;&lt;img src="old-action.png" alt="Screenshot of the run time of the old Docker-based Pulumi GitHub Action"&gt;&lt;/p&gt;
&lt;h4 id="new-javascript-based-action-powered-by-automation-api-v2"&gt;New JavaScript-based Action, powered by Automation API (v2)&lt;/h4&gt;
&lt;p&gt;&lt;img src="new-action.png" alt="Screenshot of the run time of the new Pulumi GitHub Action"&gt;&lt;/p&gt;
&lt;p&gt;The new Pulumi GitHub Action is ready for you to use. Here&amp;rsquo;s how to get started:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Already use the v1 Pulumi GitHub Action? Migrate to the new one with our &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/continuous-delivery/github-actions#migrating-from-github-action-v1"&gt;migration guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;New to Pulumi and GitHub Actions? Use our &lt;a href="https://www.pulumi.com/docs/iac/packages-and-automation/continuous-delivery/github-actions/"&gt;getting started guide&lt;/a&gt; to set up your first Pulumi workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&amp;rsquo;re eager to hear your feedback on this new Action. If you haven’t already signed up for our &lt;a href="https://slack.pulumi.com/"&gt;Community Slack&lt;/a&gt;, it’s
quick and easy! You can join in on conversations you like and get help from other community members, as well as the Pulumi Team. That’s it for now!&lt;/p&gt;</description><author>Paul Stack</author><author>Simen A. W. Olsen</author><category>github-actions</category><category>automation-api</category></item><item><title>Lambdas as Lambdas: The magic of simple serverless Functions</title><link>https://www.pulumi.com/blog/lambdas-as-lambdas-the-magic-of-simple-serverless-functions/</link><pubDate>Wed, 10 Oct 2018 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/lambdas-as-lambdas-the-magic-of-simple-serverless-functions/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/lambdas-as-lambdas-the-magic-of-simple-serverless-functions/index.png" /&gt;
&lt;p&gt;Pulumi&amp;rsquo;s approach to infrastructure as code uses familiar languages instead
of YAML or DSLs. One major advantage of this approach is that AWS
Lambdas, Azure Functions, Google Cloud Functions, et al. can just be
real lambdas in your favorite language, offering a flexible and simple path to
serverless. Such functions behave as normal functions, allowing you to
treat serverless code as part of your application instead of separate
&amp;ldquo;infrastructure&amp;rdquo; that needs to be configured, managed, and versioned
manually. In this post, we&amp;rsquo;ll examine this capability in JavaScript,
which is already very function- and callback-oriented, making serverless
feel like a natural extension of the language we already know and love.&lt;/p&gt;
&lt;p&gt;While Functions as a Service (FaaS) systems have become more popular,
getting up and running can still feel overly complex compared to normal
application development. FaaS offerings today divide the development
experience between &amp;ldquo;infrastructure&amp;rdquo; &amp;ndash; doing all the work to
configure the Lambda runtime itself (i.e. how much memory to use, what
environment variables should be present, etc.) &amp;ndash; and writing and
maintaining the code that will execute &lt;em&gt;in&lt;/em&gt; the function itself when
triggered. Most developers just want to focus on the latter, write some
code, and have it work.&lt;/p&gt;
&lt;p&gt;For &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt;, for example, the code
itself needs to written and packaged up with all its dependencies into a
single zip-file, stored as a blob that is then placed in
&lt;a href="https://aws.amazon.com/s3/"&gt;AWS S3&lt;/a&gt;. This blob is then referenced by Lambda
so that it can be loaded on-demand when necessary. Updating just a
single character of this code, like fixing a bug, then requires fully
repackaging and re-uploading everything. Compounding all of that, this
code is necessary independent of any other pieces of your application.
If the code for the Lambda needs to read or write to an S3 bucket,
publish to an &lt;a href="https://aws.amazon.com/sns/"&gt;AWS SNS&lt;/a&gt; topic, etc., it
will need to be manually configured to have that bucket&amp;rsquo;s information
passed to it, often using out of band in environment variables. These
same issues also apply when creating
&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview"&gt;FunctionApps&lt;/a&gt;
using Node.js on Azure.&lt;/p&gt;
&lt;p&gt;At Pulumi, we thought there could be a better way to do this. Why have
to jump out of your application code just to create another bit of
configuration (in YAML or JSON) just to run your application? Put
simply, we wanted developers to write event-driven code, in their
favorite language and in their favorite IDE, and simply hit &amp;ldquo;deploy&amp;rdquo;
when done.&lt;/p&gt;
&lt;h2 id="magic-functions"&gt;Magic Functions&lt;/h2&gt;
&lt;p&gt;From a desire to do better here, the &amp;ldquo;closure serialized function&amp;rdquo; was
born. However, &amp;ldquo;closure serialized function&amp;rdquo; is a geeky mouthful, so I
like just calling them &amp;ldquo;magic functions.&amp;rdquo; Our team is comprised of
cloud, language, and runtime geeks alike, so we figured out a way to
hook into the language runtimes to just make lambdas as lambdas work.&lt;/p&gt;
&lt;p&gt;Before diving more deeply into how magic functions work, let&amp;rsquo;s take a
look at how they look in action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Example simplified to not get too bogged down :)
const sharp = require(&amp;quot;sharp&amp;quot;);
const videoBucket = new aws.s3.Bucket(&amp;quot;videos&amp;quot;);
const videoTopic = new cloud.Topic(&amp;quot;videoTopic&amp;quot;);
videoBucket.onObjectCreated(&amp;quot;onNewVideo&amp;quot;, async (event) =&amp;gt; {
for (const record of event.Records) {
const dimensions = computeDimensions(record.s3.object.key);
videoTopic.publish({ s3ObjectKey: record.s3.object.key, time: record.eventTime });
}
});
function computeDimensions(objectKey: string): { height: number, width: number } {
/* simplified */
const metadata = await sharp(...);
return { height: metadata.height, width: metadata.width };
}
videoTopic.subscribe(&amp;quot;compressVideo&amp;quot;, async (event) =&amp;gt; {
const { s3ObjectKey, time } = event;
// actually compress video.
});
videoTopic.subscribe(&amp;quot;createThumbnail&amp;quot;, async (event) =&amp;gt; {
const { s3ObjectKey, time } = event;
// actually create thumbnail.
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;rsquo;s still walk through it to see what&amp;rsquo;s happening. It can be a little
subtle because we are mixing &lt;em&gt;deployment-time&lt;/em&gt; code &amp;ndash; code that
declares infrastructure as code resources &amp;ndash; and *runtime *code &amp;ndash;
application code that runs as a function.&lt;/p&gt;
&lt;p&gt;First, we&amp;rsquo;re just defining two simple resources: 1) an &lt;code&gt;s3.Bucket&lt;/code&gt;,
where we expect new video files to be uploaded to, and 2) a
&lt;code&gt;cloud.Topic&lt;/code&gt; that will inform interested parties when new videos are
uploaded. (The full surface area of AWS is available in &lt;a href="https://www.pulumi.com/docs/iac/get-started/aws/"&gt;the aws package&lt;/a&gt;, and
&lt;a href="https://github.com/pulumi/pulumi-cloud"&gt;the cloud package&lt;/a&gt; offers
multi-cloud abstractions that work at a higher level of abstraction.)
Right after defining the resources, we start creating our first FaaS
resources. &lt;code&gt;videoBucket&lt;/code&gt; has an &lt;code&gt;onObjectCreated&lt;/code&gt; event subscription
function that will fire when new objects appear, and we invoke it with a
&lt;em&gt;real&lt;/em&gt; JavaScript (or TypeScript)
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions"&gt;arrow-function&lt;/a&gt;
defining the code that will execute in an AWS Lambda at &lt;em&gt;runtime&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="not-so-fast"&gt;Not So Fast&amp;hellip;&lt;/h2&gt;
&lt;p&gt;This code is pretty magical in a few ways. To see how it&amp;rsquo;s magic, let&amp;rsquo;s
consider a fairly naive way to create a Lambda for
this &lt;code&gt;onObjectCreated&lt;/code&gt; event handler function &amp;ndash; something we need to
do by hand with FaaS solutions today. Imagine we took the raw source
text of this arrow-function by &lt;code&gt;toString&lt;/code&gt;ing it and using that as the
text of the Lambda code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// index.js
module.exports = async (event) =&amp;gt; {
foreach (var record in event.Records) {
const dimensions = computeDimensions(record.s3.object.key);
videoTopic.publish({ s3ObjectKey: record.s3.object.key, time: record.eventTime });
}
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, the above translation could have worked for an extremely basic
function, but totally breaks down here for several reasons. First,
&lt;code&gt;computeDimensions&lt;/code&gt; isn&amp;rsquo;t included, which will make this immediately
fail if ever called. Second, if &lt;code&gt;computeDimensions&lt;/code&gt; was included, it
wouldn&amp;rsquo;t work unless the &lt;code&gt;sharp&lt;/code&gt; module was properly imported for it.
Third, even if the module was imported in the code, the actual NPM
package would have to be available along with the code in the cloud.
Fourth, if that module was
properly &lt;code&gt;require&lt;/code&gt;d,
and the package was properly included, the code is both referencing
&lt;code&gt;videoTopic&lt;/code&gt; (a resource defined when the application originally ran),
&lt;em&gt;and&lt;/em&gt; it is invoking a helper method on it. Remember, this code is going
to be executing at cloud &lt;em&gt;runtime&lt;/em&gt;, not at the time when Pulumi is
actually executing the application on your developer machine, and
actually doing things like running the
&lt;code&gt;const videoTopic = new cloud.Topic(&amp;quot;videoTopic&amp;quot;)&lt;/code&gt; code.&lt;/p&gt;
&lt;p&gt;To summarize, we have quickly realized, we can&amp;rsquo;t just analyze
everything locally to figure out what has to be included, instead the
entire transitive closure must be considered &amp;ndash; a task usually left up
to FaaS developers to perform manually! This coupled with the manual
configuration using YAML can lead to plenty a headache.&lt;/p&gt;
&lt;h2 id="compilers-to-the-rescue"&gt;Compilers to the Rescue&lt;/h2&gt;
&lt;p&gt;Despite all those issues, Pulumi is able to make that original
application code work and it is able to magically effectively translate
the above into a working Lambda. The real secret sauce here is that, by
leveraging familiar languages, we can stand on the shoulders of all the
great compiler work underpinning them.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s dig in &amp;ndash; how does this all really work?&lt;/p&gt;
&lt;p&gt;(Note that, if you want some of the more nitty gritty details, we have a
&lt;a href="https://www.pulumi.com/docs/iac/concepts/miscellaneous/function-serialization/"&gt;doc&lt;/a&gt;
going in depth. For now, we&amp;rsquo;ll try to stick to providing a high-level
overview of what&amp;rsquo;s going on, so that we can grok the basic approach.)&lt;/p&gt;
&lt;p&gt;Pulumi executes your application as a normal Node.js application. As
such, when it hits the &lt;code&gt;const videoBucket = new aws.s3.Bucket(&amp;quot;videos&amp;quot;)&lt;/code&gt;
lines, it actually goes and creates those resources. When it then hits
the &lt;code&gt;videoBucket.onObjectCreated(&amp;quot;onNewVideo&amp;quot;, async (event) =&amp;gt; {&lt;/code&gt; line
this executes a normal line of JavaScript. Namely, &lt;code&gt;onObjectCreated&lt;/code&gt; is
a normal JavaScript function that will be passed the &lt;code&gt;name&lt;/code&gt; parameter as
the first arg, and the JavaScript
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions"&gt;function&lt;/a&gt;
as the second arg.
&lt;a href="https://github.com/pulumi/pulumi-aws/blob/2159b44b296ab66ce4386d42b28fb22f27a6ef6a/sdk/nodejs/s3/s3Mixins.ts#L223"&gt;onObjectCreated&lt;/a&gt;
eventually bottoms out with a call to a call to
&lt;a href="https://github.com/pulumi/pulumi/blob/fb18032a42eb34e9b5cbbe22a77a1b292d260a24/sdk/nodejs/runtime/closure/serializeClosure.ts#L89"&gt;pulumi.runtime.serializeFunction&lt;/a&gt;.
This function is the root of where our deep analysis of your JavaScript
function analysis and translation will happen.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth pausing here. We can use all the standard approaches to
function composition to build bigger functions out of smaller functions,
even before we hit that serialization routine. The closures are built up
in the V8 runtime as usual, and are eagerly awaiting the transformation
into magic functions as soon as they are needed. Pretty cool!&lt;/p&gt;
&lt;p&gt;Once we get to that point, using the awesome analysis APIs provided by
&lt;a href="https://github.com/Microsoft/TypeScript"&gt;TypeScript&lt;/a&gt;, we start
introspecting the function instance, seeing what code it contains, and
what external objects it references. The legal objects that can be
included in the captured function&amp;rsquo;s environment include (but aren&amp;rsquo;t
limited) to things like simple JavaScript objects, other functions (like
&lt;code&gt;computeDimensions&lt;/code&gt;), modules (like &lt;code&gt;sharp&lt;/code&gt;), other Pulumi resources
(like &lt;code&gt;videoTopic&lt;/code&gt;), and even complex beasts like
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"&gt;Promises&lt;/a&gt;.
Basically, anything that Pulumi knows how to serialize and rehydrate
inside the Lambda.&lt;/p&gt;
&lt;p&gt;When Pulumi determines that these objects are referenced, it
automatically includes enough information about them into the final
translated Lambda code file so that they are available and can be
effectively used at runtime. Thanks to JavaScript&amp;rsquo;s deep, dynamic
introspection capabilities, as well as its rich APIs for creating nearly
anything at runtime, it&amp;rsquo;s very possible to effectively hydrate out all
the information present when Pulumi runs, and put it into a form that
can be cheaply and accurately rehydrated into runtime objects again when
the Lambda runs.&lt;/p&gt;
&lt;p&gt;This analysis also understands the NPM package structure of an
application and its dependencies, ensuring that necessary imported
packages are properly included with the final Lambda. This analysis is
also very smart about not including things that aren&amp;rsquo;t necessary at all.
For example, it might drop information from the serialized form if it
can prove that would never be observable, in addition to relying on
built-in runtime functions being there without needing any sort of
serialization. There are definite subtleties and interesting cases that
can arise here, and for those very curious, reading the documentation
linked above may be very enlightening.&lt;/p&gt;
&lt;p&gt;Pulumi also understands diffs and dependencies, so updating is a cinch.
Any changes to the code in the JavaScript function (including anything
it depends on) is properly tracked and understood by Pulumi, allowing an
easy development model where FaaS code can be easily modified and
republished without jumping through extra hoops.&lt;/p&gt;
&lt;h2 id="the-result-simple-serverless-functions"&gt;The Result? Simple serverless Functions&lt;/h2&gt;
&lt;p&gt;With this approach, serverless programming becomes dramatically simpler,
whether it&amp;rsquo;s an AWS Lambda, Azure Function, or GCP Cloud Function.
(Stay tuned, we&amp;rsquo;re hoping to bring this to Kubernetes FaaS offerings
soon.)&lt;/p&gt;
&lt;p&gt;In short, you just write event-driven JavaScript code that&amp;rsquo;s familiar
and easy, you run &lt;code&gt;pulumi up&lt;/code&gt;, and everything is taken care of from
there. No YAML configuration files, no point and clicking in a cloud
console UI, no fuss.&lt;/p&gt;
&lt;p&gt;On top of all of that, because this is just code itself, and because
these are normal APIs, other tooling (like your editor) knows exactly
what is going on and can guide you through this. For example:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/lambdas-as-lambdas-the-magic-of-simple-serverless-functions/closure.png" alt="closure"&gt;&lt;/p&gt;
&lt;p&gt;While there&amp;rsquo;s a lot of really cool tech going on under the covers here,
we really hope that this ends up just feeling super-awesome to use for
you. Now that we have this functionality, we wouldn&amp;rsquo;t want to ever write
an AWS Lambda any other way!&lt;/p&gt;
&lt;p&gt;Get started building serverless functions on Pulumi today!&lt;/p&gt;</description><author>Cyrus Najmabadi</author><category>serverless</category><category>typescript</category></item><item><title>kubespy trace: a real-time view into of a Kubernetes Service</title><link>https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/</link><pubDate>Wed, 26 Sep 2018 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/index.png" /&gt;
&lt;p&gt;&lt;!-- spacer --&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This post is part 3 in a series on the Kubernetes API. Earlier,
&lt;a href="https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/"&gt;Part 1&lt;/a&gt;
focused on the lifecycle of a &lt;code&gt;Pod&lt;/code&gt;, and later
&lt;a href="https://www.pulumi.com/blog/how-do-kubernetes-deployments-work-an-adversarial-perspective/"&gt;Part 3&lt;/a&gt;
details how Kubernetes deployments work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why isn&amp;rsquo;t my &lt;code&gt;Pod&lt;/code&gt; getting any traffic?&lt;/p&gt;
&lt;p&gt;An experienced ops team running on GKE might assemble the following
checklist to help answer this question:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Does a &lt;code&gt;Service&lt;/code&gt; exist? Does that service have a &lt;code&gt;.spec.selector&lt;/code&gt;
that matches some number of &lt;code&gt;Pod&lt;/code&gt;s?&lt;/li&gt;
&lt;li&gt;Are the &lt;code&gt;Pod&lt;/code&gt;s alive and has their readiness probe passed?&lt;/li&gt;
&lt;li&gt;Did the &lt;code&gt;Service&lt;/code&gt; create an &lt;code&gt;Endpoints&lt;/code&gt; object that specifies one or
more &lt;code&gt;Pod&lt;/code&gt;s to direct traffic to?&lt;/li&gt;
&lt;li&gt;Is the &lt;code&gt;Service&lt;/code&gt; reachable via DNS? When you &lt;code&gt;kubectl ``exec&lt;/code&gt; into a
&lt;code&gt;Pod&lt;/code&gt; and you use &lt;code&gt;curl&lt;/code&gt; to poke the &lt;code&gt;Service&lt;/code&gt; hostname, do you get
a response? (If not, does &lt;em&gt;any&lt;/em&gt; &lt;code&gt;Service&lt;/code&gt; have a DNS entry?)&lt;/li&gt;
&lt;li&gt;Is the &lt;code&gt;Service&lt;/code&gt; reachable via IP? When you SSH into a &lt;code&gt;Node&lt;/code&gt; and
you use &lt;code&gt;curl&lt;/code&gt; to poke the &lt;code&gt;Service&lt;/code&gt; IP, do you get a response?&lt;/li&gt;
&lt;li&gt;Is &lt;code&gt;kube-proxy&lt;/code&gt; up? Is it writing iptables rules? Is it proxying to
the &lt;code&gt;Service&lt;/code&gt;?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This question might have the highest complexity-to-sentence-length ratio
of any question in the Kubernetes ecosystem. Unfortunately, it&amp;rsquo;s also a
question that &lt;em&gt;every&lt;/em&gt; user finds themselves asking at some point. And
when they do, it usually means their app is down.&lt;/p&gt;
&lt;p&gt;To help answer questions like this, we&amp;rsquo;ve been developing a small
diagnostic tool, &lt;code&gt;kubespy&lt;/code&gt;. In this post we&amp;rsquo;ll look at the new
&lt;code&gt;kubespy trace&lt;/code&gt; command, which is broadly aimed at automating questions
1, 2, 3, and providing &amp;ldquo;hints&amp;rdquo; about 4 and 5.&lt;/p&gt;
&lt;p&gt;Below is a gif demonstrating the CLI experience. You can watch in
real-time as the &lt;code&gt;Service&lt;/code&gt; comes online, finds pods to target, and
finally is allocated a public IP address:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/trace-success-ip-allocated.gif" alt="trace-success-ip-allocated"&gt;&lt;/p&gt;
&lt;h2 id="what-is-kubespy-again"&gt;What is kubespy, again?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kubespy&lt;/code&gt; is a simple, standalone diagnostic tool, meant to make it easy
to introspect on Kubernetes resources in real time.&lt;/p&gt;
&lt;p&gt;Before we begin, it&amp;rsquo;s worth noting that this &lt;code&gt;kubespy&lt;/code&gt; actually
re-packages the machinery we developed for Kubernetes support in
Pulumi.&lt;/p&gt;
&lt;p&gt;One of our major goals in this work was to make deploying an application
to Kubernetes as simple as possible, by presenting a concise summary of
this information in the CLI experience. See
&lt;a href="https://twitter.com/hausdorff_space/status/1039940379301179392"&gt;my tweetstorm&lt;/a&gt;
on the subject, or &lt;a href="https://www.pulumi.com/kubernetes/"&gt;try it out&lt;/a&gt; for
yourself!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/uploads/content/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/status-rich.gif" alt="status-rich"&gt;&lt;/p&gt;
&lt;h2 id="a-real-time-view-of-a-services-life"&gt;A real-time view of a Service&amp;rsquo;s life&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/pulumi/kubespy"&gt;&lt;code&gt;kubespy&lt;/code&gt; repository&lt;/a&gt; contains
the &lt;a href="https://github.com/pulumi/kubespy/tree/master/examples/trivial-service-trace-example"&gt;simple &lt;code&gt;trace&lt;/code&gt; example&lt;/a&gt;
we use in this demo. The
&lt;a href="https://github.com/pulumi/kubespy/tree/master/examples/trivial-service-trace-example"&gt;README&lt;/a&gt;
contains detailed installation instructions, as well as explaining how
to run the app (using either &lt;code&gt;kubetl&lt;/code&gt; or &lt;code&gt;pulumi&lt;/code&gt; though of course we
hope you will try Pulumi).&lt;/p&gt;
&lt;p&gt;Essentially: running &lt;code&gt;kubespy trace service nginx&lt;/code&gt; will cause &lt;code&gt;kubespy&lt;/code&gt;
to sit and wait for you to deploy a &lt;code&gt;Service&lt;/code&gt; called &lt;code&gt;nginx&lt;/code&gt;. When you
run this example, it will do just this: creating a &lt;code&gt;Deployment&lt;/code&gt; which
replicates an nginx &lt;code&gt;Pod&lt;/code&gt; 3 times and exposes it publicly to the
Internet with a &lt;code&gt;Service&lt;/code&gt;, also called &lt;code&gt;nginx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s break down the &lt;code&gt;kubespy trace&lt;/code&gt; gif above to show that there are
actually several distinct steps in the process of booting up a
&lt;code&gt;Service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First: &lt;code&gt;Service&lt;/code&gt; is created, the &lt;code&gt;Service&lt;/code&gt; controller creates an
&lt;code&gt;Endpoints&lt;/code&gt; object of the same name.&lt;/strong&gt; The &lt;code&gt;Endpoints&lt;/code&gt; object is to
specify which &lt;code&gt;Pod&lt;/code&gt;s get traffic &amp;mdash; their IPs, which ports to direct
traffic to, and so on. In this case, there are no &lt;code&gt;Pod&lt;/code&gt;s to target,
which &lt;code&gt;kubespy trace&lt;/code&gt; tells us:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/trace-success-create-svc.gif" alt="trace-success-create-svc"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second: &lt;code&gt;Pod&lt;/code&gt;s that match the &lt;code&gt;Service&lt;/code&gt;&amp;rsquo;s &lt;code&gt;.spec.selector&lt;/code&gt; are
created; their readiness probes immediately pass.&lt;/strong&gt; The &lt;code&gt;Endpoints&lt;/code&gt;
object is updated to reflect this. As we will see below, if the &lt;code&gt;Pods&lt;/code&gt;
failed the readiness probes, &lt;code&gt;kubespy trace&lt;/code&gt; would note this.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/trace-success-pods-ready.gif" alt="trace-success-pods-ready"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third: &lt;code&gt;Service&lt;/code&gt; is allocated a public IP address.&lt;/strong&gt; The &lt;code&gt;Service&lt;/code&gt; has
&lt;code&gt;.spec.type&lt;/code&gt; set to &lt;code&gt;LoadBalancer&lt;/code&gt;, which on most cloud platforms means
that a public IP address should be allocated for it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/trace-success-ip-allocated.gif" alt="trace-success-ip-allocated"&gt;&lt;/p&gt;
&lt;h2 id="exercise-other-service-types-watching-rollouts-deleting-services"&gt;Exercise: Other Service types, watching rollouts, deleting Services!&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kubespy trace&lt;/code&gt; supports all the other &lt;code&gt;Service&lt;/code&gt; types, including
&lt;code&gt;ExternalName&lt;/code&gt; and &lt;code&gt;ClusterIP&lt;/code&gt;. Try both of those, and you&amp;rsquo;ll see
slightly different output. Try them! It&amp;rsquo;s also worth watching what
happens when a &lt;code&gt;Service&lt;/code&gt; is deleted.&lt;/p&gt;
&lt;p&gt;You can also use &lt;code&gt;kubespy trace&lt;/code&gt; to watch an unhealthy deployment become
healthy. In the following gif, we see a bunch of &lt;code&gt;Pod&lt;/code&gt;s that are failing
readiness checks become healthy as a new version is rolled out:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/become-alive.gif" alt="become-alive"&gt;&lt;/p&gt;
&lt;h2 id="conclusions"&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Confession time. Last time we told you we&amp;rsquo;d dig more into the lifecycle
of a &lt;code&gt;Pod&lt;/code&gt;. And we will, at some point. But we ended up deciding that it
would be easier to explain with a cohesive &lt;code&gt;trace&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;And, while this is a good start, it is only the beginning. &lt;code&gt;trace&lt;/code&gt;
currently supports only &lt;code&gt;Service&lt;/code&gt;. In our next post, we&amp;rsquo;ll extend trace
to &lt;code&gt;Deployment&lt;/code&gt; (or perhaps &lt;code&gt;ReplicaSet&lt;/code&gt;), and from there, we will have
enough tools to really dig into what is happening when you roll out your
app.&lt;/p&gt;
&lt;p&gt;In the mean time, if you enjoyed this post, or are curious to see how
this lifecycle is baked into the Pulumi CLI, &lt;a href="https://www.pulumi.com/kubernetes/"&gt;give it a spin&lt;/a&gt;!
We&amp;rsquo;d love to have your feedback.&lt;/p&gt;</description><author>Alex Clemmer</author><category>kubernetes</category></item><item><title>Advanced TypeScript type FTW!</title><link>https://www.pulumi.com/blog/advanced-typescript-type-ftw/</link><pubDate>Wed, 19 Sep 2018 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/advanced-typescript-type-ftw/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/advanced-typescript-type-ftw/index.png" /&gt;
&lt;p&gt;We at Pulumi love TypeScript for cloud apps and infrastructure, because of its rich type system and great ahead-of-time
typechecking – making for a more productive inner loop and helping to find errors sooner. The typesystem magic behind
how this works for infrastructure as code can be fascinating!&lt;/p&gt;
&lt;p&gt;A core part of the Pulumi &lt;a href="https://www.pulumi.com/docs/concepts/"&gt;programming model&lt;/a&gt; is that we allow people to express complex
&lt;a href="https://www.pulumi.com/docs/concepts/inputs-outputs/"&gt;dependency data&lt;/a&gt; that may &lt;em&gt;eventually&lt;/em&gt; be available.
Traditional JavaScript programming might expose that as a Promise&lt;T&gt;, but we’ve taken that one step further by introducing
a type we call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Input&amp;lt;T&amp;gt; = T | Promise&amp;lt;T&amp;gt; | Output&amp;lt;T&amp;gt;;
// Like a Promise&amp;lt;T&amp;gt;, but also keeps track of dependency information so we can figure
// out what the upstream/downstream impact is whenever any values change in the system
interface Output&amp;lt;T&amp;gt; { /* ... */ }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This complex type can often show up as inputs to all Pulumi objects like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface VpcInput {
ipAddresses: Input&amp;lt;Input&amp;lt;string&amp;gt;[]&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Breaking that down, it’s basically saying “I’ll take an array of strings. But those actual string values can individually be
strings, promises-of-strings, or strings-carrying-dependency-information.” It even goes one step beyond that, because the
entire array itself could be just an array-of-those-things, or a promise-of-array-of-those-things, or an
array-of-those-things-with-dependency-information. Whew…!&lt;/p&gt;
&lt;p&gt;This approach is great for people producing the data. As a producer I can do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new Vpc({ ipAddresses: [&amp;quot;an-address&amp;quot;] }); // or even
new Vpc({ ipAddresses: [&amp;quot;an-address&amp;quot;, computeAddressAsynchronously() ]}); // or even
new Vpc({ ipAddresses: [&amp;quot;an-address&amp;quot;, computeAddressAsynchronously(), someDependency.address ]}); // or even
new Vpc({ ipAddresses: computeAnArrayAnyOfTheAboveAsynchrousnously() }); // etc. etc.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s super flexible, and let’s people naturally produce the values in whatever way they want without having to work too hard to
make it conform to some very narrow type.&lt;/p&gt;
&lt;p&gt;However, while easy to produce, this can sometimes be difficult to consume. For example, if you wanted to take in that
&lt;code&gt;Input&amp;lt;Input&amp;lt;string&amp;gt;[]&amp;gt;&lt;/code&gt; and then check if there was a very specific value in it, you might have to go through and do a bunch of
work. You’d have to first deal with potentially getting a &lt;code&gt;Input&amp;lt;string&amp;gt;[]&lt;/code&gt; or a &lt;code&gt;Promise&amp;lt;Input&amp;lt;string&amp;gt;[]&amp;gt;&lt;/code&gt; or even an &lt;code&gt;Output&amp;lt;Input&amp;lt;string&amp;gt;[]&amp;gt;&lt;/code&gt;.
Then, once you even got to the underlying array, you’d have to deal with each potential element in it being a &lt;code&gt;string&lt;/code&gt;… or a &lt;code&gt;Promise&amp;lt;string&amp;gt;&lt;/code&gt;… or a… well… you get the idea. It’s not fun 😃.&lt;/p&gt;
&lt;p&gt;Up until now, we’d supplied some simple helpers to make this more manageable. Our helpers would effectively ‘unwrap’ one
layer of this sort of structure allowing you take the external value and deal with an internal value of a known shape.
So it effectively worked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function unwrap&amp;lt;T&amp;gt;(input: Input&amp;lt;T&amp;gt;): T { /* ... */ }
// so, you could then write:
const ipAddresses: Input&amp;lt;string&amp;gt;[] = unwrap(vpc.ipAddresses);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helped things somewhat. But it still meant that as you processed that array you had to then unwrap each and every
element. As our data model has grown, and we’ve tackled more and more complex domains, we started seeing code start to
form the programming &lt;a href="https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)"&gt;Pyramid of Doom&lt;/a&gt;, where you just kept
on going deeper and deeper, unwrapping all the way. We started seeing this happen so often that we wanted to see if we
there was anything we could do to make life much nicer for people using our APIs and using these helpers.&lt;/p&gt;
&lt;p&gt;Our core goal was to change our ‘unwrap’ function from just peeling away one layer of this onion, to peeling away &lt;em&gt;all&lt;/em&gt;
layers. So, for example, if you started with an &lt;code&gt;Input&amp;lt;Input&amp;lt;string&amp;gt;[]&amp;gt;&lt;/code&gt; you would just get back a plain old &lt;code&gt;string[]&lt;/code&gt;
that you could use completely naturally in all the ways you’d expect. We were initially uncertain if we could make
this work, but thanks to the power of JavaScript and the expressivity of TypeScript, we think we found a great solution
here.&lt;/p&gt;
&lt;p&gt;We knew that providing the implementation such a function itself was not going to the true hard part. After all, we
already had the code to peel back one layer. So peeling back all the layers was more a matter of just having the function
call itself recursively when encountering things like arrays (or other objects that themselves contained &lt;code&gt;Input&lt;/code&gt;s).&lt;/p&gt;
&lt;p&gt;The real hard part came in when were trying figure out how would we even express this in a type-safe manner TypeScript.
After all, what’s the type signature when you effectively want to say:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function unwrap(input: Input&amp;lt;...MaybeHasInputsOrPromisesOrOutputsArbitrarilyDeep...&amp;gt;): ThatThingWithAllTheDeepInputsPromisesAndOutputsErased;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was a big head scratcher for a while. But after exploring some of the powerful new features that TypeScript has
been bringing about in recent releases, we finally found a great way to manage it. In the end, it ends up looking
something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type primitive = string | number | boolean | undefined | null;
type Unwrap&amp;lt;T&amp;gt; =
T extends Promise&amp;lt;infer U&amp;gt; ? UnwrapSimple&amp;lt;U&amp;gt; :
T extends Output&amp;lt;infer U&amp;gt; ? UnwrapSimple&amp;lt;U&amp;gt; :
UnwrapSimple&amp;lt;T&amp;gt;;
type UnwrapSimple&amp;lt;T&amp;gt; =
T extends primitive ? T :
T extends Array&amp;lt;infer U&amp;gt; ? UnwrappedArray&amp;lt;U&amp;gt; :
T extends object ? UnwrappedObject&amp;lt;T&amp;gt; :
never;
interface UnwrappedArray&amp;lt;T&amp;gt; extends Array&amp;lt;Unwrap&amp;lt;T&amp;gt;&amp;gt; {}
type UnwrappedObject&amp;lt;T&amp;gt; = {
[P in keyof T]: Unwrap&amp;lt;T[P]&amp;gt;;
};
function unwrap&amp;lt;T&amp;gt;(val: T): Unwrap&amp;lt;T&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s a bit of a doozy containing a bunch of type definitions and the final function declaration that uses them. It
makes use of many advanced TypeScript typing features. Including ‘Union Types’ (the ‘primitive’ type), ‘Mapped Types’
(which ‘UnwrappedObject’ is an example of), ‘Conditional Types’ (the types with &lt;code&gt;?&lt;/code&gt; and &lt;code&gt;:&lt;/code&gt; in them) and ‘Inferred Types’
(the use of &lt;code&gt;infer U&lt;/code&gt; in several places). You can read more about these in the
&lt;a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html"&gt;Advanced Types&lt;/a&gt; documentation.&lt;/p&gt;
&lt;p&gt;Put together, the &lt;code&gt;Unwrap&amp;lt;T&amp;gt;&lt;/code&gt; itself is effectively a recursively defined structural pattern matching type. You can see
it reference itself once you walk into &lt;code&gt;UnwrappedArray&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;UnwrappedObject&amp;lt;T&amp;gt;&lt;/code&gt; types. As it recurses, it matches
the type it has and then ‘peels away’ the wrappings that have been caused by &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;Output&amp;lt;T&amp;gt;&lt;/code&gt;. As it hits
Arrays and Objects, it dives deeper, unwrapping along the way. This is all manageable in TypeScript because it does this
sort of matching and unwrapping in a lazy fashion, allowing us to express what is effectively an infinitely large
pattern-matching type, without it having any trouble understanding it or applying it!&lt;/p&gt;
&lt;p&gt;It amazes us how well this works and how expressive TypeScript is here. As you can see here, TypeScript completely
understands what’s going on and will give you all the great type-checking support and tooling that we know and love:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/advanced-typescript-type-ftw/completion-list.png" alt="TypeScript completion list"&gt;&lt;/p&gt;
&lt;p&gt;Since adding support for this, we’ve been able to start using this new function across our own codebase and we’ve seen
things get dramatically simpler. The pyramid-of-doom goes away and code becomes much cleaner and easier to read and
maintain. JavaScript helped us write this system easily, but thanks to TypeScript we can express these complex intents
and have the safety and understanding to do this sort of thing with confidence.&lt;/p&gt;
&lt;p&gt;A special thanks to &lt;a href="https://github.com/danielrosenwasser"&gt;@DanielRosenwasser&lt;/a&gt; over from the TypeScript team for
putting up with my constant pestering on how to work through this and deal with some especially thorny areas!&lt;/p&gt;</description><author>Cyrus Najmabadi</author><category>typescript</category></item><item><title>Kubespy, and the lifecycle of a Pod, in 4 images</title><link>https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/</link><pubDate>Tue, 18 Sep 2018 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/index.png" /&gt;
&lt;p&gt;&lt;!-- spacer --&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This post is the first part in a series on the Kubernetes API. Future installments include
&lt;a href="https://www.pulumi.com/blog/kubespy-trace-a-real-time-view-into-the-heart-of-a-kubernetes-service/"&gt;Part 2&lt;/a&gt;
focused on the lifecycle of a &lt;code&gt;Service&lt;/code&gt; and
&lt;a href="https://www.pulumi.com/blog/how-do-kubernetes-deployments-work-an-adversarial-perspective/"&gt;Part 3&lt;/a&gt;
details how Kubernetes deployments work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the most popular features of the recent
&lt;a href="https://www.pulumi.com/blog/cloud-native-infrastructure-with-kubernetes-and-pulumi/"&gt;v0.15.2 release&lt;/a&gt;
of Pulumi is fine-grained status updates for Kubernetes resources. On
the CLI they look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/uploads/content/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/status-rich.gif" alt="status-rich"&gt;&lt;/p&gt;
&lt;p&gt;But wait &amp;mdash; how does this work exactly? What is &lt;em&gt;actually happening&lt;/em&gt;
when you deploy a &lt;code&gt;Pod&lt;/code&gt; to a cluster? How does a &lt;code&gt;Service&lt;/code&gt; go from
uninitialized, to being allocated a public IP address? How often is my
&lt;code&gt;Deployment&lt;/code&gt;&amp;rsquo;s status changing?&lt;/p&gt;
&lt;p&gt;To answer these questions and others like them, today we&amp;rsquo;re releasing
&lt;strong&gt;a small tool, &lt;a href="https://github.com/pulumi/kubespy"&gt;&lt;code&gt;kubespy&lt;/code&gt;&lt;/a&gt;,&lt;/strong&gt;
purpose-built to help you answer questions like this, by displaying the
changes made to a Kubernetes object in real time. For example, in the
following gif, we&amp;rsquo;re running &lt;code&gt;kubespy status v1 Pod nginx&lt;/code&gt; to watch the
changes to a &lt;code&gt;Pod&lt;/code&gt;&amp;rsquo;s status as it is booted up.&lt;/p&gt;
&lt;p&gt;We will spend the rest of this short post using &lt;code&gt;kubespy&lt;/code&gt; to take a
closer look at what happens to a &lt;code&gt;Pod&lt;/code&gt; when it&amp;rsquo;s deployed to the
cluster.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/status.gif" alt="status"&gt;&lt;/p&gt;
&lt;h2 id="what-happens-when-you-boot-up-a-pod"&gt;What happens when you boot up a Pod?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;kubespy&lt;/code&gt; comes with a &lt;a href="https://github.com/pulumi/kubespy/tree/master/examples/trivial-pulumi-example"&gt;simple example&lt;/a&gt;
that deploys nginx using a naked &lt;code&gt;Pod&lt;/code&gt;. (The example works with both the
Pulumi CLI and &lt;code&gt;kubectl&lt;/code&gt;, but we hope you&amp;rsquo;ll give Pulumi a shot!)&lt;/p&gt;
&lt;p&gt;The essential gist is that &lt;code&gt;kubespy&lt;/code&gt; will sit and watch for the example
&lt;code&gt;Pod&lt;/code&gt; to be created or changed; once you deploy the &lt;code&gt;Pod&lt;/code&gt; (either
&lt;code&gt;pulumi up&lt;/code&gt; or &lt;code&gt;kubectl apply&lt;/code&gt;), you will see something like the gif
above.&lt;/p&gt;
&lt;p&gt;If you do this yourself, you will see that there are 4 updates reported
by &lt;code&gt;kubespy&lt;/code&gt;, each of which individually gives us a fairly clear picture
of what Kubernetes is doing internally to try to run the &lt;code&gt;Pod&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First: Acknowledging the &lt;code&gt;Pod&lt;/code&gt; definition is written to etcd.&lt;/strong&gt;
The API server receives the &lt;code&gt;Pod&lt;/code&gt; definition and begins trying to schedule it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/1-created.png" alt="1-created"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second: Scheduling the &lt;code&gt;Pod&lt;/code&gt;.&lt;/strong&gt;
The scheduler successfully schedules the &lt;code&gt;Pod&lt;/code&gt; to run on a node.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/2-scheduled.png" alt="2-scheduled"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third: Creating the &lt;code&gt;Pod&lt;/code&gt;.&lt;/strong&gt;
The kubelet running on the node receives the &lt;code&gt;Pod&lt;/code&gt; definition and begins &amp;ldquo;creating&amp;rdquo;
the &lt;code&gt;Pod&lt;/code&gt;. This involves pulling the
container image, adding volume mounts, &lt;em&gt;etc&lt;/em&gt;. At the end of all of this,
it will attempt to run the container.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/3-creating.png" alt="3-creating"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fourth: Marking the &lt;code&gt;Pod&lt;/code&gt; as &lt;code&gt;Running&lt;/code&gt;.&lt;/strong&gt;
The kubelet has now resolved the image
tag to a specific SHA, pulled it to the node, has successfully applied
the required configuration, and has successfully started the &lt;code&gt;nginx&lt;/code&gt;
container. In this case, the &lt;code&gt;Pod&lt;/code&gt; contains only one container to run,
and once it&amp;rsquo;s initialized, the whole &lt;code&gt;Pod&lt;/code&gt; gets marked running.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/kubespy-and-the-lifecycle-of-a-kubernetes-pod-in-four-images/4-running.png" alt="4-running"&gt;&lt;/p&gt;
&lt;h2 id="exercises"&gt;Exercises:&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Try inducing an error and see what &lt;code&gt;kubespy&lt;/code&gt; outputs! Make the image
tag nonsense, add a volume that doesn&amp;rsquo;t exist, and so on.&lt;/li&gt;
&lt;li&gt;Try booting up a service! See what &lt;code&gt;kubespy&lt;/code&gt; displays under various
configurations!  &lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusions-and-the-subtleties-of-the-pod-api"&gt;Conclusions, and the subtleties of the Pod API.&lt;/h2&gt;
&lt;p&gt;This post shows a fairly basic, happy-path use of &lt;code&gt;Pods&lt;/code&gt;, and shows that
we can &lt;code&gt;kubespy status&lt;/code&gt; to see exactly what our containers are up to.&lt;/p&gt;
&lt;p&gt;But wait &amp;mdash; what does &lt;code&gt;Running&lt;/code&gt; mean, precisely? If a &lt;code&gt;Pod&lt;/code&gt; is
&lt;code&gt;Running&lt;/code&gt;, does that mean it&amp;rsquo;s healthy? If a &lt;code&gt;Pod&lt;/code&gt; has multiple
containers, and one crashes, does it still get marked &lt;code&gt;Running&lt;/code&gt;? Are
there any other settings that can change when a &lt;code&gt;Pod&lt;/code&gt; is marked
&lt;code&gt;Running&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Unfortunately, though the &lt;code&gt;Pod&lt;/code&gt; is the atomic compute abstraction of
Kubernetes (or perhaps &lt;em&gt;because&lt;/em&gt; of this), the answer to these questions
turns out to be fairly subtle. But, because &lt;code&gt;Pod&lt;/code&gt;s are so important, we
should endeavor to answer them anyway!&lt;/p&gt;
&lt;p&gt;In the next post, we will &lt;code&gt;kubespy&lt;/code&gt; to observe some of the subtleties of
the &lt;code&gt;Pod&lt;/code&gt; lifecycle, and especially the effect of &lt;code&gt;restartPolicy&lt;/code&gt; on the
semantics of the &lt;code&gt;Pod&lt;/code&gt; object. We will also talk about some of the best
practices we&amp;rsquo;ve accumulated, and describe how we solved the problem of
repeatable &lt;code&gt;Pod&lt;/code&gt; deployments in Pulumi.&lt;/p&gt;
&lt;p&gt;If you enjoyed this post, or are curious to see how this lifecycle is
baked into the Pulumi CLI, &lt;a href="https://www.pulumi.com/kubernetes/"&gt;give us a shot&lt;/a&gt;!
We&amp;rsquo;d love to have your feedback.&lt;/p&gt;</description><author>Alex Clemmer</author><category>kubernetes</category></item><item><title>How we use Pulumi to build Pulumi</title><link>https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/</link><pubDate>Tue, 26 Jun 2018 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/how-we-use-pulumi-to-build-pulumi/index.png" /&gt;
&lt;p&gt;Here at Pulumi we are (perhaps unsurprisingly!) huge fans of using
Pulumi to manage our cloud infrastructure and services. We author our
infrastructure in strongly-typed programming languages, which allows us
to to benefit from rich tooling - documenting and factoring our
infrastructure using the same software engineering practices we apply to
our application code. This also allows us to create reusable
abstractions which accelerate our ability to deliver new features and
services, and our ability to standardize and refactor infrastructure
patterns across our services with relative ease.&lt;/p&gt;
&lt;p&gt;Like other users, we use Pulumi at a variety of levels of abstraction.
We use Pulumi for raw infrastructure provisioning, defining the core
networking layer for our AWS-based backend infrastructure. And we use
Pulumi to define how our application services are deployed into ECS
using just a few lines of code. Pulumi hosts and manages static content
for &lt;a href="https://www.pulumi.com/"&gt;www.pulumi.com&lt;/a&gt; and &lt;a href="https://get.pulumi.com"&gt;get.pulumi.com&lt;/a&gt;.
We use Pulumi to
define the CloudWatch dashboards connected to our infrastructure. And
for monitoring, Pulumi defines metrics and notifications/alarms in
PagerDuty and Slack.&lt;/p&gt;
&lt;p&gt;Best of all, we&amp;rsquo;ve been able to take things we&amp;rsquo;ve learned from these use
cases, and others we&amp;rsquo;ve worked with beta users on over the last few
months (thank you!), and factor common patterns out into reusable
libraries like &lt;code&gt;@pulumi/aws-infra&lt;/code&gt; and &lt;code&gt;@pulumi/cloud&lt;/code&gt; for ourselves and
others to build upon.&lt;/p&gt;
&lt;p&gt;In this post, we&amp;rsquo;ll do a deeper dive into each of these use cases,
highlighting unique aspects of how we use Pulumi itself, and some of our
engineering processes around how we integrate Pulumi into the rest of
our toolchain.&lt;/p&gt;
&lt;h2 id="infrastructure"&gt;Infrastructure&lt;/h2&gt;
&lt;p&gt;Our backend infrastructure that supports
&lt;a href="https://app.pulumi.com/signin"&gt;app.pulumi.com&lt;/a&gt; and the Pulumi CLI is fairly
typical of a cloud service/application building on AWS. Those who have
done this before will be familiar with the combination of AWS VPC, EC2,
ASG, ALB, ECS, S3, Aurora, CloudFront and Route53 that powers the core
of many AWS-based web applications.&lt;/p&gt;
&lt;p&gt;We started off building this infrastructure last year - before we even
had completed support for managing AWS resources in Pulumi! As a result,
we brought this up initially using CloudFormation. Moving this to Pulumi
was an amazing experience. We immediately brought more engineering
rigour to the codebase, factoring and decomposing functionality in
clearer ways, extracting out useful patterns into named functions, using
loops and conditionals instead of copy/paste, and getting completion
lists and on-the-fly error checking from within our development tools.&lt;/p&gt;
&lt;p&gt;Equally important, authoring our infrastructure in Pulumi gave us the
ability to factor out reusable abstractions. We found that our own VPC
definition was something that many early customers we worked with also
needed - so we factored it out into &lt;code&gt;new awsinfra.Network&lt;/code&gt;. We found
that standing up an ECS cluster was another big pile of AWS resources
that are commonly needed, but can be configured with a much smaller
space of configuration values in the common case, so we factored out
&lt;code&gt;new awsinfra.Cluster&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/carbon.png" alt="carbon"&gt;&lt;/p&gt;
&lt;p&gt;Defining infrastructure in Pulumi made it easy to stand up many
instances of our service, including per-developer stacks, with just
&lt;code&gt;pulumi stack init my-developer-stack&lt;/code&gt; and &lt;code&gt;pulumi update&lt;/code&gt;. The ability
to build reusable components which can be multi-instantiated into the
same target cloud is critical here, and something that Pulumi takes care
of by default. Within minutes, a developer can have an isolated copy of
our full infrastructure stood up, which can be used to test out and
quickly iterate on changes. This includes not just the basic
infrastructure, but also provisioning of DNS entries in Route53 and
creation of custom CloudWatch dashboards (see more on this below).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/image-2.png" alt="image-2"&gt;&lt;/p&gt;
&lt;p&gt;We have three shared environments - &lt;strong&gt;development&lt;/strong&gt;, &lt;strong&gt;staging&lt;/strong&gt; and
&lt;strong&gt;production&lt;/strong&gt; - each an isolated environment with separate
configuration. For these shared environments we drive deployments of the
Pulumi programs that define the infrastructure above via our CI/CD
pipeline. Pulumi can be integrated into any existing CI/CD pipeline or
toolchain, via direct use of &lt;code&gt;pulumi preview&lt;/code&gt; and &lt;code&gt;pulumi update&lt;/code&gt; as
part of build and deployment stages. This allows us to reliably deliver
a known desired state for each target environment, and to promote
changes between environments with just a &lt;code&gt;git merge&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="api-and-app-services"&gt;API and App Services&lt;/h2&gt;
&lt;p&gt;Most of our backend application services are developed in Go, and we
deploy them onto our production infrastructure as container images.
However, instead of building separate CI pipelines for our container
images and the infrastructure they need to function correctly (ECS
Service, Application Load Balancer, ECR Repository, etc.), we drive the
deployment of both infrastructure and application code via a single
Pulumi program. This allows our application code to version along with
its supporting infrastructure - when we need to expose a new port for
example, we can make the changes to both application code and load
balancer as part of a single commit, and promote it through CI as an
atomic action. We have found this to significantly simplify the delivery
of containerized services on AWS, as well as making it easy to stand up
new services into our existing shared networking and cluster
infrastructure.&lt;/p&gt;
&lt;p&gt;Again, we found this to be a common pattern as we worked with other
early customers, and factored out a library to simplify deployment of
containerized services into a production deployment on AWS as part of
the &lt;code&gt;cloud.Service&lt;/code&gt; API in &lt;code&gt;@pulumi/cloud&lt;/code&gt;. Here&amp;rsquo;s an example of using
an application service defined in the &lt;code&gt;./airflow-container&lt;/code&gt; folder (with
a &lt;code&gt;Dockerfile&lt;/code&gt;) into an ECS cluster. This one declaration will provision
a scaled-out ECS Service for running the container images, a ECR
repository to store the image, optionally a load balancer if ports were
exposed, and each &lt;code&gt;pulumi update&lt;/code&gt; will build the container image, push
it to the private repository, and turn-over each service task to use the
new image. All of that workflow complexity is abstracted away behind a
simple API and a single CLI command.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/image-3.png" alt="image-3"&gt;&lt;/p&gt;
&lt;h2 id="metrics-alarms-and-dashboards"&gt;Metrics, Alarms and Dashboards&lt;/h2&gt;
&lt;p&gt;We also use Pulumi to define a few less traditional parts of our
infrastructure - including our metrics, dashboards and alarming
infrastructure on top of AWS CloudWatch.&lt;/p&gt;
&lt;p&gt;Defining and versioning this along with our infrastructure has
significant benefits: new features are built with metrics, dashboards
and alarms lit up from the get-go, developers get a full set of
dashboards as part of their personal isolated development environments,
and promotions from development to staging to production can verify not
just physical infrastructure, but also that alarms and dashboards are
behaving as expected prior to pushing code to production. Here&amp;rsquo;s a
sample of our CloudWatch dashboards:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/image-4.png" alt="image-4"&gt;&lt;/p&gt;
&lt;p&gt;We build up AWS resources representing CloudWatch dashboards using a
compositional framework shown below. As this example shows, bringing
software engineering approaches to infrastructure as code can lead to
new approaches to how code is authored and factored - and ultimately to
the creation of higher level frameworks for building domain-specific
cloud infrastructure - in this case, CloudWatch dashboards.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/image-5.png" alt="carbon"&gt;&lt;/p&gt;
&lt;p&gt;The same metrics that power these dashboards are also used to power
notifications and alarms. In these cases, we want to define both the
infrastructure for the alarm - the SNS topics and registration of those
topics with CloudWatch - as well as the small piece of code that fires
off Pagerduty and Slack notifications on notifications being pushed to
that SNS topic - all in one codebase. This ensures that we can version
these together, and that we can deploy both consistently as part of
updates. To do this, we include the event handlers for these
infrastructure events directly inline in our Pulumi programs. Note in
the code snippet below how we define the infrastructure for a &lt;code&gt;Topic&lt;/code&gt;,
and then conditionally wire up event handlers on that topic, including
the code to execute at runtime on these events directly in our Pulumi
program. This code can even reach out and reference configuration used
to provision our Pulumi deployment - key to enabling this to be deployed
seamlessly into a variety of different development environments.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/image-6.png" alt="image-6"&gt;&lt;/p&gt;
&lt;h2 id="static-content"&gt;Static Content&lt;/h2&gt;
&lt;p&gt;We serve three properties as static content from S3, each described
using Pulumi. One of these is
&lt;a href="https://get.pulumi.com/"&gt;https://get.pulumi.com&lt;/a&gt; which hosts downloads
of our &lt;code&gt;pulumi&lt;/code&gt; CLI installer. It&amp;rsquo;s a very simple program, and even
better - it&amp;rsquo;s &lt;a href="https://github.com/pulumi/get.pulumi.com"&gt;open source on GitHub&lt;/a&gt;.
It combines AWS S3, CloudFront and Route53.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/image-7.png" alt="image-7"&gt;&lt;/p&gt;
&lt;p&gt;This infrastructure is deployed and managed using the Pulumi Service at
&lt;a href="https://app.pulumi.com/signin"&gt;app.pulumi.com&lt;/a&gt;, allowing us to track the
history of deployments and changes to the infrastructure and quickly
jump in to the resources defined by this stack in the AWS console.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/how-we-use-pulumi-to-build-pulumi/image-8.png" alt="image-8"&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Treating cloud &lt;a href="https://www.pulumi.com/what-is/what-is-infrastructure-as-software/"&gt;infrastructure as software&lt;/a&gt;
has changed how we think about infrastructure engineering. Using Pulumi for both traditional cloud
infrastructure as well as to get application code and supporting service
infrastructure into production provides a simple workflow and the
ability to create simple high-level APIs to make new services easy to
define and deploy.&lt;/p&gt;
&lt;p&gt;Try out Pulumi yourself today!&lt;/p&gt;</description><author>Luke Hoban</author><category>serverless</category><category>aws</category><category>containers</category></item></channel></rss>