<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Lee Briggs</title>
    <description>The latest articles on Forem by Lee Briggs (@jaxxstorm).</description>
    <link>https://forem.com/jaxxstorm</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F449382%2Fd4a9e07d-36f1-4c34-96c9-3823b66c3382.jpeg</url>
      <title>Forem: Lee Briggs</title>
      <link>https://forem.com/jaxxstorm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jaxxstorm"/>
    <language>en</language>
    <item>
      <title>Structuring your Infrastructure as Code</title>
      <dc:creator>Lee Briggs</dc:creator>
      <pubDate>Fri, 18 Aug 2023 14:02:41 +0000</pubDate>
      <link>https://forem.com/jaxxstorm/structuring-your-infrastructure-as-code-2nco</link>
      <guid>https://forem.com/jaxxstorm/structuring-your-infrastructure-as-code-2nco</guid>
      <description>&lt;p&gt;If you're thinking of migrating to another infrastructure as code tool (and why would you, everything is &lt;em&gt;great&lt;/em&gt; in the IaC world now, right?!) you might find yourself asking yourself a fundamental question when you get started: how do I structure things in a way that scales well and stands the test of time?&lt;/p&gt;

&lt;p&gt;There's no canonical answer. Everyone does things slightly different, and different tools have different ideas on the best way.&lt;/p&gt;

&lt;p&gt;In my day to day role as a Solutions Engineer at &lt;a href="https://pulumi.com"&gt;Pulumi&lt;/a&gt; I get to answer this question a &lt;em&gt;lot&lt;/em&gt;. Customers are migrating from other IaC tools and they want to take this opportunity to think about the way they'd like to structure things.&lt;/p&gt;

&lt;p&gt;This blog post is designed to detail my high(ish) level thoughts on the concepts and principals I like to use, and why. As we explore these concepts, I'll talk about some of the lessons I learned from my time in configuration management and the myriad IaC tools I've used before today.&lt;/p&gt;

&lt;p&gt;A lot of the concepts in this post are focused on &lt;a href="https://pulumi.com"&gt;Pulumi&lt;/a&gt;, but lots are broadly applicable to other tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layers
&lt;/h2&gt;

&lt;p&gt;I'm sure my system administrator background is showing, but I like to think about infrastructure through the concept of &lt;em&gt;layers&lt;/em&gt; similar to the &lt;a href="https://en.wikipedia.org/wiki/OSI_model"&gt;OSI Model&lt;/a&gt;. Most of the layers I'll outline here closely mirror the OSI model, but what you'll likely want to do before you create your Git repo or write a single line of code, is group your cloud infrastructure into layers. The reason why will become apparent later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 0: Billing
&lt;/h3&gt;

&lt;p&gt;The billing layer is where you sign up or input your credit card. Each cloud provider does this differently&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS: Organization&lt;/li&gt;
&lt;li&gt;Azure: Account&lt;/li&gt;
&lt;li&gt;Google Cloud: Account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my experience, while there's API for this stuff you likely &lt;em&gt;don't&lt;/em&gt; want to manage this layer with IaC, so do yourself a favour and do it manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Privilege
&lt;/h3&gt;

&lt;p&gt;The privilege layer is how you fundementally separate access in the cloud provider. Again, each provider does this a little differently.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS: Account&lt;/li&gt;
&lt;li&gt;Azure: Subscription&lt;/li&gt;
&lt;li&gt;Google Cloud: Project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;em&gt;might&lt;/em&gt; want to manage this layer with IaC, but you need to decide how that'd work. Personally, I find that the API level support for this layer and the rarity of needing to perform this operation means it's often easier to manage this layer manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: Network
&lt;/h3&gt;

&lt;p&gt;Now we're getting to the layers that'll should &lt;em&gt;definitely&lt;/em&gt; be managed by IaC. The network layer is foundational to how everything will work in your infrastructure, and includes things like a VPC, subnets, NAT Gateways, VPNs, and anything else that facilitates network communication.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS: VPCs, Subnets, Route Tables, Internet Gateways, NAT Gateways, VPNs&lt;/li&gt;
&lt;li&gt;Azure: Virtual Networks, Subnets, Route Tables, Internet Gateways, NAT Gateways, VPNs&lt;/li&gt;
&lt;li&gt;Google Cloud: VPCs, Subnets, Route Tables, Internet Gateways, Cloud Nat, VPNs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 3: Permissions
&lt;/h3&gt;

&lt;p&gt;Now we've laid down a network layer, we need to allow other people or applications to talk to the cloud provider API. IAM roles, or service principals live in this layer.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS: IAM Roles, IAM Users, IAM Groups&lt;/li&gt;
&lt;li&gt;Azure: Service Principals, Managed Identities&lt;/li&gt;
&lt;li&gt;Google Cloud: Service Accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 4: Data
&lt;/h3&gt;

&lt;p&gt;The data layer is where the resources you're managing really start to open up. This is where you'll find things like databases, object stores, message queues, and anything else that's used to store or transfer data.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS: RDS, DynamoDB, S3, SQS, SNS, Kinesis, Redshift, DocumentDB, ElastiCache, DynamoDB&lt;/li&gt;
&lt;li&gt;Azure: SQL, CosmosDB, Blob Storage, Queue Storage, Event Grid, Event Hubs, Service Bus, Redis Cache&lt;/li&gt;
&lt;li&gt;Google Cloud: Cloud SQL, Cloud Spanner, Cloud Storage, Cloud Pub/Sub, Cloud Datastore, Cloud Bigtable, Cloud Memorystore&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 5: Compute
&lt;/h3&gt;

&lt;p&gt;The compute layer is where your applications actually run - this is where you'll find things like virtual machines, containers, and serverless functions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS: EC2, ECS, EKS, Fargate&lt;/li&gt;
&lt;li&gt;Azure: Virtual Machines, Container Instances, AKS&lt;/li&gt;
&lt;li&gt;Google Cloud: Compute Engine, GKE&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 6: Ingress
&lt;/h3&gt;

&lt;p&gt;Layer 6 is where you'll find the resources that allow your applications to be accessed by the outside world.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS: Application Load Balancers, Network Load Balancers, Classic Load Balancers, API Gateways&lt;/li&gt;
&lt;li&gt;Azure: Application Gateways, Load Balancers, API Management&lt;/li&gt;
&lt;li&gt;Google Cloud: Load Balancers, API Gateways&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 7: Application
&lt;/h3&gt;

&lt;p&gt;Once we've provisioned all the supporting infrastructure, we now need to actually deploy the application itself. This is where things really get a little tricky and depend entirely on your application's deployment model, technology and architecture.&lt;/p&gt;

&lt;p&gt;You might choose not to use IaC for application at all, but if you do..&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS: Lambda, ECS Tasks, Kubernetes Manifests, EC2 User Data&lt;/li&gt;
&lt;li&gt;Azure: Azure Functions, Kubernetes Manifests&lt;/li&gt;
&lt;li&gt;Google Cloud: Cloud Functions, Kubernetes Manifests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Visualization
&lt;/h3&gt;

&lt;p&gt;If you're a visual learner like me, you might find this visualization helpful:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Example Resources&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Billing&lt;/td&gt;
&lt;td&gt;AWS Organization/Azure Account/Google Cloud Account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Privilege&lt;/td&gt;
&lt;td&gt;AWS Account/Azure Subscription/Google Cloud Project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;AWS VPC/Google Cloud VPC/Azure Virtual Network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Permissions&lt;/td&gt;
&lt;td&gt;AWS IAM/Azure Managed Identity/Google Cloud Service Account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;AWS RDS/Azure Cosmos DB/Google Cloud SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Compute&lt;/td&gt;
&lt;td&gt;AWS EC2/Azure Container Instances/GKE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Ingress&lt;/td&gt;
&lt;td&gt;AWS ELB/Azure Load Balancer/Google Cloud Load Balancer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Kubernetes Manifests/Azure Functions/ECS Taks/Google Cloud Functions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Principal 1: The Rate of Change
&lt;/h3&gt;

&lt;p&gt;One of the difficult concepts to quantify when thinking of these layers is the &lt;em&gt;rate of change&lt;/em&gt; that these resources undergo when you're managing them. The lower layers generally will change &lt;em&gt;less frequently&lt;/em&gt; than your higher layers, and in addition to this these layers are generally most fraught with risk when changing them.&lt;/p&gt;

&lt;p&gt;You might be wondering why this matter - the answer to this is because when structuring your IaC, you'll want to consider how you group resources together in your chosen IaC's encapsulation mechanism. For example, Pulumi uses the concept of &lt;a href="https://www.pulumi.com/docs/concepts/projects/"&gt;&lt;em&gt;projects&lt;/em&gt;&lt;/a&gt; to group resources together.&lt;/p&gt;

&lt;p&gt;When creating and defining resources in a Pulumi project, the fundamental consideration you need think of when adding a resource is "which layer does this resource live in?". You generally shouldn't have resources from different layers in the same project, because the rate of change of those resources will be different and the risk of changing them is different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Principal 2: Resource Lifecycle
&lt;/h3&gt;

&lt;p&gt;As with all principals in life, there are situations where principal 1 doesn't broadly apply.&lt;/p&gt;

&lt;p&gt;There are resources within the above layers where you might think "ah! this is a network resources so I'll put it in my network project" but the &lt;em&gt;lifecycle&lt;/em&gt; of the resource doesn't necessarily fit as a shared resource. A great example of this is an AWS security group.&lt;/p&gt;

&lt;p&gt;Security groups are generally specific to another resource - perhaps an application you're deploying, a loadbalancer that's shared or maybe a database in the data layer. With these resources, it's generally best to consider the overall lifecycle of the dependent resources when deciding where to put it.&lt;/p&gt;

&lt;p&gt;My rule of thumb here is this - if I wanted to provision this resource in a different environment, or better yet, destroy it - what other resources do I want to destroy at the same time?&lt;/p&gt;

&lt;p&gt;Another great considering for this is the permissions layer. I already mentioned when discussing permissions that you'll need to think about that layer as &lt;em&gt;shared&lt;/em&gt; permissions, application specific permissions are entirely different - they really want to go directly with your application deployment code.&lt;/p&gt;

&lt;p&gt;The summary here is: don't be afraid to break the first principal, but make sure when you're doing it you're thinking about the resource lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Principal 3: Repositories
&lt;/h3&gt;

&lt;p&gt;The mono-repo vs multi-repo debate is one that's will rage long after we're all done with cloud computing and have migrated back to physical infrastructure, and I'm not going to try and solve it here. What I &lt;em&gt;will&lt;/em&gt; say is that I've seen both work well, and both work poorly.&lt;/p&gt;

&lt;p&gt;When it comes to IaC repositories, I again come back to our layering system and make differing decisions for where the code for deploying the repo should live is based on the layer.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Control Repo
&lt;/h4&gt;

&lt;p&gt;For the foundational, shared aspects of the infrastructure, I generally like to include those projects in a single mono-repo which back in my days of using &lt;a href="https://www.puppet.com/"&gt;Puppet&lt;/a&gt; we called a &lt;a href="https://github.com/puppetlabs/control-repo"&gt;control repo&lt;/a&gt;. I still like to use this nomenclature.&lt;/p&gt;

&lt;p&gt;If we refer back to our layers, I generally like to ensure layers 1 and 2 in this shared repo. Things get a &lt;em&gt;little&lt;/em&gt; trickier once we get to layer 3, the permissions layer. At this stage, we need to decide if the resource itself is shared or not. A good example of this is an IAM role that might be used for &lt;em&gt;human&lt;/em&gt; users instead of application user. This is generally going to be shared across multiple humans and teams, so it's a good candidate for the control repo.&lt;/p&gt;

&lt;p&gt;Layer 4 really depends on your application architecture. If you have a message bus spanning multiple applications, putting it in your control repo probably makes sense, but if you have a database that is only used by a single application, you likely don't want it in the control repo for a variety of reasons.&lt;/p&gt;

&lt;p&gt;Layer 5 again depends on your organisation's permission model and cloud architecture. It's not uncommon to share shared compute like an ECS cluster or Kubernetes cluster which spans many applications, so including it in an application repo probably isn't going to make much sense. However if you're isolating compute on a per-application basis, you're almost certainly going to want to make this application specific.&lt;/p&gt;

&lt;p&gt;Layer 6: As it likely becoming an obvious trend, you'll need to take your application architecture and permission model into account. If you're using a shared load balancer and routing traffic that way, you'll likely want to include it in the control repo, but if you're using a per-application load balancer you'll want to include it in the application repo.&lt;/p&gt;

&lt;h4&gt;
  
  
  Application Repositories
&lt;/h4&gt;

&lt;p&gt;At the very least, if you're using IaC to deploy your application, having a &lt;code&gt;deploy/&lt;/code&gt; directory in your application repo is a great starting point. If you use Pulumi and want to use the same language as your application to do your deployments, you might consider having all of your dependencies in a single &lt;code&gt;package.json&lt;/code&gt; or &lt;code&gt;requirements.txt&lt;/code&gt; depending on your chosen language.&lt;/p&gt;

&lt;p&gt;You'll need to think about the rate of change here when you're defining projects to group resources together. Do you perhaps need to separate your database layer and your application layer resources? I'd argue that you do, because the rate of change of your application layer is likely going to be much higher than your database layer, but you'll need to make a decision that makes sense for your organisation and project.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why do this?
&lt;/h4&gt;

&lt;p&gt;The primary reason for making the decision to use both mono-repos and keeping deployment code with applications is built from a perspective of &lt;em&gt;ownership&lt;/em&gt; and &lt;em&gt;orchestration&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Foundational infrastructure at layers 1, 2 and possibly up to layer 5 is an order of operations problem and a workflow orchestration problem. In most circumstances, you'll be creating resources that depend on &lt;em&gt;other&lt;/em&gt; resources while building the IaC graph. &lt;/p&gt;

&lt;p&gt;By deciding to break the resources into different projects, you can create a workflow that allows you to deploy the resources in the correct order. You'll be able to utilize Pulumi &lt;a href="https://www.pulumi.com/learn/building-with-pulumi/stack-references/"&gt;stack references&lt;/a&gt; to share resources between stacks and projects, but you'll need to ensure that a resource in a project in layer 2 that depends on a project in layer 1 has been created and resolved first.&lt;/p&gt;

&lt;p&gt;In a mono-repo, this is as simple as ensuring that the workflow or CI/CD tool runs the projects in the correct order, but in a multi-repo implementation, it becomes a complex orchestration problem that likely involves multi repo webhooks and a lot of duct tape.&lt;/p&gt;

&lt;p&gt;Application repos are far enough down the layering system that all of the infrastructure required to run your application will be in place. Placing application deployment infrastructure code in the application repo allows you to give the application developers full ownership of their code from writing and features to getting them into production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Principal 3: Encapsulation
&lt;/h3&gt;

&lt;p&gt;Once you've made the foundational decisions above, you'll be well on the way to structuring a well defined set of infrastructure as code patterns, but the final thing you'll need to consider is how you'll share resource patterns across your control repo and application repos.&lt;/p&gt;

&lt;p&gt;Every IaC tool has a different way of managing this. In Pulumi you can create a &lt;a href="https://www.pulumi.com/docs/concepts/resources/components/"&gt;Component Resource&lt;/a&gt; for a single language or if you want to support multiple language, you might want to create a &lt;a href="https://www.pulumi.com/docs/using-pulumi/pulumi-packages/"&gt;Pulumi Package&lt;/a&gt;, but the reason for doing this is the same: you want to encapsulate a set of best practices that you can share across multiple projects.&lt;/p&gt;

&lt;p&gt;A good consideration for for when to start encapsulating resources is to think about your organisational structure and application architecture. If you're only one team deploying a single application, you might not need to go down the path of encapsulating anything, but if you're a platform team that's likely to support dozens of teams to deploy to a shared layer 5 compute resource, creating a Pulumi package that encapsulates the best practices for deploying your application or creating a package for a best practice object storage bucket which has the required permissions is going to save the teams you're supporting a &lt;em&gt;lot&lt;/em&gt; of time.&lt;/p&gt;

&lt;p&gt;These encapsulations should be in their own, &lt;em&gt;distinct&lt;/em&gt; repository. You'll want to version these encapsulations in the same way you version and release your applications - follow semver and make sure you create an API that your downstream users can use.&lt;/p&gt;

&lt;p&gt;As your downstream users start to depend on these encapsulations, you can introduce concepts like &lt;a href="https://www.pulumi.com/docs/using-pulumi/testing/unit/"&gt;unit testing&lt;/a&gt; to make sure you &lt;a href="https://lkml.org/lkml/2012/12/23/75"&gt;don't break userspace&lt;/a&gt; with your infrastructure.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pitfalls
&lt;/h4&gt;

&lt;p&gt;A common mistake I see at the encapsulation layer when adopting Pulumi is trying to avoid object orientated principles and using a what I like to call the "function based approach".&lt;/p&gt;

&lt;p&gt;As an example of this, you might try and encapsulate some resources into a function. In TypeScript it'd look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createBucket&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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="nc"&gt;Bucket&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in Python like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_bucket&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 class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem with this implementation of an abstraction is that it creates a nested mechanism that is difficult to manage successfully.&lt;/p&gt;

&lt;p&gt;If you use a component, you get you get an abstraction mechanism that is much more native to the way the language works. In TypeScript, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bucket&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ComponentResource&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&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;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="nf"&gt;constructor&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BucketArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&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;ComponentResourceOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lbrlabs:index:Bucket&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&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="nc"&gt;Bucket&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="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bucket&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;ComponentResource&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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 class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BucketArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;ResourceOptions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lbrlabs:index:Bucket&lt;/span&gt;&lt;span class="sh"&gt;"&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 class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&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 class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;While the instantiation of the resource is more complex, the manageability of this over time is exponentially easier. Trust me, I've untangled this mess before.&lt;/p&gt;

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

&lt;p&gt;An example is worth a thousand words, so let's take a look at a hypothetical control repo and application repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control Repo
&lt;/h3&gt;

&lt;p&gt;Let's say we're going to be super original and call our repo &lt;code&gt;infrastructure&lt;/code&gt;. Here's how that might look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── certs
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── __main__.py
│   ├── requirements.txt
│   └── venv
├── cluster
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── README.md
│   ├── __main__.py
│   ├── requirements.txt
│   └── venv
├── shared_database
│   ├── Pulumi.development.yaml
|   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── __main__.py
│   ├── components
│   ├── requirements.txt
│   └── venv
├── domains
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── __main__.py
│   ├── requirements.txt
│   └── venv
├── cache
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── __main__.py
│   ├── requirements.txt
│   └── venv
├── shared_example_app
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── README.md
│   ├── __main__.py
│   ├── productionapp.py
│   ├── requirements.txt
│   └── venv
├── shared_bucket
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── __main__.py
│   ├── requirements.txt
│   └── venv
├── vpc
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   ├── __main__.py
│   ├── requirements.txt
│   └── venv
└── vpn
    ├── Pulumi.development.yaml
    ├── Pulumi.production.yaml
    ├── Pulumi.yaml
    ├── __main__.py
    ├── requirements.txt
    └── venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see here that we're using &lt;a href="https://www.pulumi.com/docs/concepts/stack/"&gt;Pulumi stacks&lt;/a&gt; to target differing environments (in this case, development and production), and creating a new project for different layers and resources.&lt;/p&gt;

&lt;p&gt;You'll likely also notice that I've been quite liberal with my use of directories for each set of services. I'm not grouping all of the network/layer 2 resources into a single project, however I'm following the layering principal by not grouping any resources from different layers into the same project.&lt;/p&gt;

&lt;p&gt;You can definitely reduce the number of projects here (for example, you might choose to groups the VPC and VPN projects together in a &lt;code&gt;network&lt;/code&gt; project) but I generally find that projects/directories are "free" and reducing the blast radius of changes makes people feel comfortable about contributing to these shared elements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Repo
&lt;/h3&gt;

&lt;p&gt;Once we get to our application repo, it's a lot harder to be prescriptive, but let's say we have a simple Go application called &lt;code&gt;example-app&lt;/code&gt;. Here's how that might look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── Dockerfile
├── Makefile
├── docker-compose.yml
├── deploy
│   ├── Pulumi.development.yaml
│   ├── Pulumi.production.yaml
│   ├── Pulumi.yaml
│   └── main.go
├── go.mod
├── go.sum
├── main.go
├── readme.md
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully this is fairly self explanatory, you've got your application and mechanisms for local development with a &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;Makefile&lt;/code&gt;, and we can put our Pulumi code in a &lt;code&gt;deploy/&lt;/code&gt; directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encapsulation Repo
&lt;/h3&gt;

&lt;p&gt;Finally, let's take a look at an example encapsulation repo. These repos can be quite complex, so as an example, take a look at this Pulumi package which encapsulates some level compute &lt;a href="https://github.com/lbrlabs/pulumi-lbrlabs-eks"&gt;here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;We've covered a lot of ground here, but hopefully this has given you some ideas on how you might want to structure your infrastructure as code. If you're using Pulumi, as with all my content, always open to hearing better ideas!&lt;/p&gt;

</description>
      <category>infrastructureascode</category>
      <category>pulumi</category>
      <category>terraform</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Stop using static cloud credentials in GitHub Actions</title>
      <dc:creator>Lee Briggs</dc:creator>
      <pubDate>Sun, 23 Jan 2022 23:20:02 +0000</pubDate>
      <link>https://forem.com/jaxxstorm/stop-using-static-cloud-credentials-in-github-actions-45il</link>
      <guid>https://forem.com/jaxxstorm/stop-using-static-cloud-credentials-in-github-actions-45il</guid>
      <description>&lt;p&gt;Picture the scene. You're configuring your automation pipelines, whether it's deploying infrastructure, applications or any other piece of your CI/CD pipeline that needs to access a cloud provider. You want to do things properly, so you define a well scoped role with a minimum set of permissions that you need for the pipeline to be successful. Then you assign those permissions to your cloud provider's authentication mechanism. If you're lucky, your CI/CD pipeline runs in the cloud too, so you never need to define a set of static credentials.&lt;/p&gt;

&lt;p&gt;If you're not using self hosted runners for your CI/CD pipeline, you might pause for a second. "I need to remember to rotate these credentials" you think. Maybe you'll set a reminder to rotate them in a month's time, or perhaps you'll set up some elaborate mechanism to rotate them. More likely than not, you'll forget about it completely until your wonderful InfoSec team bug you about them, hopefully it'll be for a compliance reasons and not because someone got hold of them.&lt;/p&gt;

&lt;p&gt;Until recently, these hard coded credentials have been not only dangeorus, but unavoidable. Mechanisms for accessing cloud provider from &lt;em&gt;outside&lt;/em&gt; the cloud provider itself have been almost non-existent. You defined an IAM user/service principal/service account/insert other mechanism here and you just...hoped. &lt;/p&gt;

&lt;p&gt;In addition to these credentials being static and hard to rotate, often the credentials stored in CI/CD services can have extremely broad and permissive privileges. If you're running infrastructure automation, for example, you might need to scope the credentials to basically admin permissions, which is an extremely worrying prospect.&lt;/p&gt;

&lt;p&gt;The good news is, this is starting to change, and a well defined protocol is in the middle of these changes. &lt;/p&gt;

&lt;p&gt;If you're using GitHub Actions to as your CI/CD tool of choice, you can now use OIDC with the 3 major cloud providers to securely authenticate to that provider. You can find a long, well defined document in the GitHub documentation &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect"&gt;here&lt;/a&gt;. This document clearly states the benefit of using OIDC, but for posterity, we'll repeat them here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;No cloud secrets&lt;/em&gt;: You won't need to duplicate your cloud credentials as long-lived GitHub secrets. Instead, you can configure the OIDC trust on your cloud provider, and then update your workflows to request a short-lived access token from the cloud provider through OIDC.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Authentication and authorization management&lt;/em&gt;: You have more granular control over how workflows can use credentials, using your cloud provider's authentication (authN) and authorization (authZ) tools to control access to cloud resources.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rotating credentials&lt;/em&gt;: With OIDC, your cloud provider issues a short-lived access token that is only valid for a single workflow run, and then automatically expires.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This all sounds pretty amazing right? No cloud credentials?! How do I set this up?&lt;/p&gt;

&lt;p&gt;In the rest of this blog post, we'll look at how you can use &lt;a href="https://pulumi.com"&gt;Pulumi&lt;/a&gt;'s TypeScript SDKs to quickly an easy set up GitHub actions, so you don't have to manually configure the access!&lt;/p&gt;

&lt;p&gt;It's of course quite possible to use Pulumi's other SDKs, as well as other infrastructure as code tools to do the setup, but we'll use Pulumi in this walkthrough. If you don't want to read a whole blog post, you can go directly to the code &lt;a href="https://github.com/jaxxstorm/secure-cloud-access"&gt;here&lt;/a&gt; with example actions running to show you this does really work, honest.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To jump directly to your cloud provider, use these links: AWS | Azure | Google Cloud&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  AWS
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The complete code for this example can be found &lt;a href="https://github.com/jaxxstorm/secure-cloud-access/tree/main/aws"&gt;here&lt;/a&gt;"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AWS has leaned into OIDC as an authentication mechanism since they introduced &lt;a href="https://aws.amazon.com/blogs/opensource/introducing-fine-grained-iam-roles-service-accounts/"&gt;IAM roles for service accounts&lt;/a&gt; back in 2019. The ability to use OIDC as an authentication mechanism has also been extended to other services, and GitHub actions is one of them.&lt;/p&gt;

&lt;p&gt;We'll need the &lt;a href="https://www.pulumi.com/registry/packages/aws/"&gt;Pulumi AWS provider&lt;/a&gt; in order to interact with AWS, as well as the &lt;a href="https://www.pulumi.com/registry/packages/github/"&gt;GitHub provider&lt;/a&gt;, so make sure you've got those installed in your Pulumi program like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;npm install @pulumi/aws @pulumi/github&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create an OIDC Provider
&lt;/h3&gt;

&lt;p&gt;The first step in being able to use OIDC in GitHub actions is to define an OIDC provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oidcProvider&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;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OpenIdConnectProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;thumbprintLists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6938fd4d98bab03faadb97b34396831e3780aea1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;clientIdLists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/jaxxstorm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sts.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://token.actions.githubusercontent.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL is important here, as it the thumprint. You can essentially copy and paste these static values. The &lt;code&gt;clientIDList&lt;/code&gt; needs to be updated to use your GitHub organization, and this can be used across repositories within your GitHub Org.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define an IAM Role
&lt;/h3&gt;

&lt;p&gt;Next up, we'll need to define an IAM role. We'll set a condition on this IAM role to scope the role to repository in the &lt;code&gt;Condition&lt;/code&gt; field. You can scope the access to anything that exists in the &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token"&gt;OIDC token&lt;/a&gt; - in this example we're allowing all, because YOLO.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;role&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;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secure-cloud-access&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access for github.com/jaxxstorm/secure-cloud-access&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;assumeRolePolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2012-10-17&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sts:AssumeRoleWithWebIdentity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;StringLike&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token.actions.githubusercontent.com:sub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;repo:jaxxstorm/secure-cloud-access:*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// replace with your repo&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;Federated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;oidcProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&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;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PolicyDocument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up, we'll need to attach a policy to this role, to define what this repository will be able to do in AWS. In this example I'm going to just add `ReadOnly permissions, but we'll need to be considerate about what this repo is going to do.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
// get our AWS account ID&lt;br&gt;
const partition = aws.getPartition();&lt;/p&gt;

&lt;p&gt;// Attack the readonlyaccess policy&lt;br&gt;
partition.then((p) =&amp;gt; {&lt;br&gt;
  new aws.iam.PolicyAttachment("readOnly", {&lt;br&gt;
    policyArn: &lt;code&gt;arn:${p.partition}:iam::aws:policy/ReadOnlyAccess&lt;/code&gt;,&lt;br&gt;
    roles: [role.name],&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Our final step is to use Pulumi's &lt;code&gt;GitHub&lt;/code&gt; provider to store the role name in a GitHub actions secret, so we can quickly and easy access it from a workflow:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
new github.ActionsSecret("roleArn", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "ROLE_ARN",&lt;br&gt;
  plaintextValue: role.arn,&lt;br&gt;
});&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your final Pulumi program will look a bit like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
import * as aws from "@pulumi/aws";&lt;br&gt;
import * as github from "@pulumi/github"&lt;/p&gt;

&lt;p&gt;const oidcProvider = new aws.iam.OpenIdConnectProvider("secure-cloud-access", {&lt;br&gt;
  thumbprintLists: ["6938fd4d98bab03faadb97b34396831e3780aea1"],&lt;br&gt;
  clientIdLists: ["&lt;a href="https://github.com/jaxxstorm"&gt;https://github.com/jaxxstorm&lt;/a&gt;", "sts.amazonaws.com"],&lt;br&gt;
  url: "&lt;a href="https://token.actions.githubusercontent.com"&gt;https://token.actions.githubusercontent.com&lt;/a&gt;",&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const role = new aws.iam.Role("secure-cloud-access", {&lt;br&gt;
  description: "Access for github.com/jaxxstorm/secure-cloud-access",&lt;br&gt;
  assumeRolePolicy: {&lt;br&gt;
    Version: "2012-10-17",&lt;br&gt;
    Statement: [&lt;br&gt;
      {&lt;br&gt;
        Action: ["sts:AssumeRoleWithWebIdentity"],&lt;br&gt;
        Effect: "Allow",&lt;br&gt;
        Condition: {&lt;br&gt;
          StringLike: {&lt;br&gt;
            "token.actions.githubusercontent.com:sub":&lt;br&gt;
              "repo:jaxxstorm/secure-cloud-access:*",&lt;br&gt;
          },&lt;br&gt;
        },&lt;br&gt;
        Principal: {&lt;br&gt;
          Federated: [oidcProvider.arn],&lt;br&gt;
        },&lt;br&gt;
      },&lt;br&gt;
    ],&lt;br&gt;
  } as aws.iam.PolicyDocument,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const partition = aws.getPartition();&lt;/p&gt;

&lt;p&gt;partition.then((p) =&amp;gt; {&lt;br&gt;
  new aws.iam.PolicyAttachment("readOnly", {&lt;br&gt;
    policyArn: &lt;code&gt;arn:${p.partition}:iam::aws:policy/ReadOnlyAccess&lt;/code&gt;,&lt;br&gt;
    roles: [role.name],&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;new github.ActionsSecret("roleArn", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "ROLE_ARN",&lt;br&gt;
  plaintextValue: role.arn,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;export const roleArn = role.arn;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Define your GitHub Actions workflow
&lt;/h3&gt;

&lt;p&gt;Now, let's define a workflow to verify what credentials we got:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`yaml&lt;/p&gt;

&lt;h1&gt;
  
  
  The workflow Creates static website using aws s3
&lt;/h1&gt;

&lt;p&gt;name: AWS Workflow&lt;br&gt;
on:&lt;br&gt;
  push&lt;br&gt;
permissions:&lt;br&gt;
  id-token: write&lt;br&gt;
  contents: read&lt;br&gt;
jobs:&lt;br&gt;
  CheckAccess:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - name: Git clone the repository&lt;br&gt;
        uses: actions/checkout@v2&lt;br&gt;
      - name: configure aws credentials&lt;br&gt;
        uses: aws-actions/configure-aws-credentials@master&lt;br&gt;
        with:&lt;br&gt;
          role-to-assume: ${{ secrets.ROLE_ARN }}&lt;br&gt;
          role-session-name: githubactions&lt;br&gt;
          aws-region: us-west-2&lt;br&gt;
      - name:  Check permissions&lt;br&gt;
        run: |&lt;br&gt;
          aws sts get-caller-identity&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You'll notice we're passing the &lt;code&gt;ROLE_ARN&lt;/code&gt; from the repository secret directly, so we don't have to hardcode anything. Now, run your Pulumi program to create all the AWS resources needed, and check everything in. You should have access to AWS with &lt;code&gt;ReadOnly&lt;/code&gt; access, without having to specify any AWS credentials!&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The complete code for this example can be found &lt;a href="https://github.com/jaxxstorm/secure-cloud-access/tree/main/azure"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To configure "credentialless access" in Azure, we can follow a similar pattern. We'll need to use Pulumi's &lt;a href="https://www.pulumi.com/registry/packages/azuread/"&gt;Azure AD&lt;/a&gt; provider, the &lt;a href="https://www.pulumi.com/registry/packages/github/"&gt;GitHub&lt;/a&gt; as well as the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/"&gt;Azure Native&lt;/a&gt; provider. We're also going to use the Azure SDK to make our life a little easier, so make sure you've run the following in your Pulumi program before you proceed:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;yaml&lt;br&gt;
npm install @pulumi/azure-native @pulumi/azuread @pulumi/github @azure/arm-authorization @azure/ms-rest-js&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an Azure AD Application and Service Principal
&lt;/h3&gt;

&lt;p&gt;Our first step is to define the user that GitHub actions will use to get its permissions. We create an Azure AD Application, a Service Principal and a random password, like so:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
// create an azure AD application&lt;br&gt;
const adApp = new azuread.Application("gha", {&lt;br&gt;
  displayName: "githubActions",&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// create a service principal&lt;br&gt;
const adSp = new azuread.ServicePrincipal(&lt;br&gt;
  "ghaSp",&lt;br&gt;
  { applicationId: adApp.applicationId },&lt;br&gt;
  { parent: adApp }&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;// mandatory SP password&lt;br&gt;
const adSpPassword = new azuread.ServicePrincipalPassword(&lt;br&gt;
  "aksSpPassword",&lt;br&gt;
  {&lt;br&gt;
    servicePrincipalId: adSp.id,&lt;br&gt;
    endDate: "2099-01-01T00:00:00Z",&lt;br&gt;
  },&lt;br&gt;
  { parent: adSp }&lt;br&gt;
);&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Federated Identity Credential
&lt;/h3&gt;

&lt;p&gt;Now here comes the magic. We're going to create a Federated Identity credential, which has a subject for our repository in it. Azure is stricter about the subject than AWS, so we'll need to define what part of the OIDC token we want to allow access. In this example, I'm allowing the main branch access, but you can use any part of the &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token"&gt;OIDC token&lt;/a&gt; like the environment.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
/*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the magic. We set the subject to the repo we're running from&lt;/li&gt;
&lt;li&gt;Also need to ensure your AD Application is the one where access is defined
*/
new azuread.ApplicationFederatedIdentityCredential(
"gha",
{
audiences: ["api://AzureADTokenExchange"],
subject: "repo:jaxxstorm/secure-cloud-access:ref:refs/heads/main", // this can be any ref
issuer: "&lt;a href="https://token.actions.githubusercontent.com"&gt;https://token.actions.githubusercontent.com&lt;/a&gt;",
applicationObjectId: adApp.objectId,
displayName: "github-actions",
},
{ parent: adApp }
);
`&lt;code&gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Define a permissions this application gets
&lt;/h3&gt;

&lt;p&gt;Now we've got the federated identity credential, we need to create a role assignment. There's going to be a lot to unpack here, so let's look at the code first, then walk through it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;/p&gt;

&lt;p&gt;import { AuthorizationManagementClient } from "@azure/arm-authorization";&lt;br&gt;
import { TokenCredentials } from "@azure/ms-rest-js";&lt;br&gt;
import * as authorization from "@pulumi/azure-native/authorization";&lt;/p&gt;

&lt;p&gt;async function getAuthorizationManagementClient(): Promise {&lt;br&gt;
  const config = await authorization.getClientConfig();&lt;br&gt;
  const token = await authorization.getClientToken();&lt;br&gt;
  const credentials = new TokenCredentials(token.token);&lt;br&gt;
  // Note: reuse the credentials and/or the client in case your scenario needs&lt;br&gt;
  // multiple calls to Azure SDKs.&lt;br&gt;
  return new AuthorizationManagementClient(credentials, config.subscriptionId);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;async function getRoleIdByName(&lt;br&gt;
  roleName: string,&lt;br&gt;
  scope?: string&lt;br&gt;
): Promise {&lt;br&gt;
  const client = await getAuthorizationManagementClient();&lt;br&gt;
  const roles = await client.roleDefinitions.list(scope || "", {&lt;br&gt;
    filter: &lt;code&gt;roleName eq '${roleName}'&lt;/code&gt;,&lt;br&gt;
  });&lt;br&gt;
  if (roles.length === 0) {&lt;br&gt;
    throw new Error(&lt;code&gt;role "${roleName}" not found at scope "${scope}"&lt;/code&gt;);&lt;br&gt;
  }&lt;br&gt;
  if (roles.length &amp;gt; 1) {&lt;br&gt;
    throw new Error(&lt;br&gt;
      &lt;code&gt;too many roles "${roleName}" found at scope "${scope}". Found: ${roles.length}&lt;/code&gt;&lt;br&gt;
    );&lt;br&gt;
  }&lt;br&gt;
  const role = roles[0];&lt;br&gt;
  return role.id!;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;const subInfo = authorization.getClientConfig();&lt;/p&gt;

&lt;p&gt;subInfo.then((info) =&amp;gt; {&lt;br&gt;
    new authorization.RoleAssignment("readOnly", {&lt;br&gt;
    principalId: adSp.id,&lt;br&gt;
    principalType: authorization.PrincipalType.ServicePrincipal,&lt;br&gt;
    scope: pulumi.interpolate&lt;code&gt;/subscriptions/${info.subscriptionId}&lt;/code&gt;,&lt;br&gt;
    roleDefinitionId: getRoleIdByName("Reader"),&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Huge thanks to &lt;a href="https://twitter.com/mikhailshilkov"&gt;Mikhail Shilkov&lt;/a&gt; here for his prior art on retrieving the &lt;code&gt;roleDefinitionId&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why do we need all this? Well, we could hard code the &lt;code&gt;roleDefinitionId&lt;/code&gt; but looking it up is cleaner. Let's step through it:&lt;/p&gt;

&lt;p&gt;First we define an auth client to talk to Azure using the Azure TypeScript SDK&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
async function getAuthorizationManagementClient():  Promise&amp;lt;AuthorizationManagementClient&amp;gt; {&lt;br&gt;
  const config = await authorization.getClientConfig();&lt;br&gt;
  const token = await authorization.getClientToken();&lt;br&gt;
  const credentials = new TokenCredentials(token.token);&lt;br&gt;
  // Note: reuse the credentials and/or the client in case your scenario needs&lt;br&gt;
  // multiple calls to Azure SDKs.&lt;br&gt;
  return new AuthorizationManagementClient(credentials, config.subscriptionId);&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, we define a function which can look up an Azure role by its name, rather than the long string you define it by:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
async function getRoleIdByName(&lt;br&gt;
  roleName: string,&lt;br&gt;
  scope?: string&lt;br&gt;
): Promise&amp;lt;string&amp;gt; {&lt;br&gt;
  const client = await getAuthorizationManagementClient();&lt;br&gt;
  const roles = await client.roleDefinitions.list(scope || "", {&lt;br&gt;
    filter:&lt;/code&gt;roleName eq '${roleName}'&lt;code&gt;,&lt;br&gt;
  });&lt;br&gt;
  if (roles.length === 0) {&lt;br&gt;
    throw new Error(&lt;/code&gt;role "${roleName}" not found at scope "${scope}"&lt;code&gt;);&lt;br&gt;
  }&lt;br&gt;
  if (roles.length &amp;gt; 1) {&lt;br&gt;
    throw new Error(&lt;br&gt;
&lt;/code&gt;too many roles "${roleName}" found at scope "${scope}". Found: ${roles.length}&lt;code&gt;&lt;br&gt;
    );&lt;br&gt;
  }&lt;br&gt;
  const role = roles[0];&lt;br&gt;
  return role.id!;&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, we use &lt;code&gt;azure-native&lt;/code&gt;'s &lt;code&gt;authorization&lt;/code&gt; package to get the current client information:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
const subInfo = authorization.getClientConfig();&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now, we can create a role assignment for our service principal that defines the ReadOnly permission:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
subInfo.then((info) =&amp;gt; {&lt;br&gt;
    new authorization.RoleAssignment("readOnly", {&lt;br&gt;
    principalId: adSp.id,&lt;br&gt;
    principalType: authorization.PrincipalType.ServicePrincipal,&lt;br&gt;
    scope: pulumi.interpolate&lt;/code&gt;/subscriptions/${info.subscriptionId}&lt;code&gt;,&lt;br&gt;
    roleDefinitionId: getRoleIdByName("Reader"),&lt;br&gt;
  });&lt;br&gt;
});&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At this stage, our service principal has read only permissions on the subscription we're using. Now, let's allow GitHub actions to use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define GitHub secrets
&lt;/h3&gt;

&lt;p&gt;Our final step is to define the GitHub secrets that we use in our workflow:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
subInfo.then((info) =&amp;gt; {&lt;/p&gt;

&lt;p&gt;// define some github actions secrets so your AZ login is correct&lt;br&gt;
  new github.ActionsSecret("tenantId", {&lt;br&gt;
    repository: "secure-cloud-access",&lt;br&gt;
    secretName: "AZURE_TENANT_ID",&lt;br&gt;
    plaintextValue: info.tenantId,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;new github.ActionsSecret("subscriptionId", {&lt;br&gt;
    repository: "secure-cloud-access",&lt;br&gt;
    secretName: "AZURE_SUBSCRIPTION_ID",&lt;br&gt;
    plaintextValue: info.subscriptionId,&lt;br&gt;
  });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// finally, we set the client id to be the application we created&lt;br&gt;
new github.ActionsSecret("clientId", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "AZURE_CLIENT_ID",&lt;br&gt;
  plaintextValue: adApp.applicationId,&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Note, we need to define the &lt;code&gt;tenantId&lt;/code&gt; and &lt;code&gt;subcriptionId&lt;/code&gt; inside the promise returned by the &lt;code&gt;subInfo&lt;/code&gt; call. The &lt;code&gt;clientId&lt;/code&gt; is set to our Azure AD application client id.&lt;/p&gt;

&lt;p&gt;Our complete Pulumi program looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
import * as pulumi from "@pulumi/pulumi";&lt;br&gt;
import * as authorization from "@pulumi/azure-native/authorization";&lt;br&gt;
import * as azuread from "@pulumi/azuread";&lt;br&gt;
import * as github from "@pulumi/github";&lt;/p&gt;

&lt;p&gt;import { AuthorizationManagementClient } from "@azure/arm-authorization";&lt;br&gt;
import { TokenCredentials } from "@azure/ms-rest-js";&lt;/p&gt;

&lt;p&gt;async function getAuthorizationManagementClient(): Promise {&lt;br&gt;
  const config = await authorization.getClientConfig();&lt;br&gt;
  const token = await authorization.getClientToken();&lt;br&gt;
  const credentials = new TokenCredentials(token.token);&lt;br&gt;
  // Note: reuse the credentials and/or the client in case your scenario needs&lt;br&gt;
  // multiple calls to Azure SDKs.&lt;br&gt;
  return new AuthorizationManagementClient(credentials, config.subscriptionId);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;async function getRoleIdByName(&lt;br&gt;
  roleName: string,&lt;br&gt;
  scope?: string&lt;br&gt;
): Promise {&lt;br&gt;
  const client = await getAuthorizationManagementClient();&lt;br&gt;
  const roles = await client.roleDefinitions.list(scope || "", {&lt;br&gt;
    filter: &lt;code&gt;roleName eq '${roleName}'&lt;/code&gt;,&lt;br&gt;
  });&lt;br&gt;
  if (roles.length === 0) {&lt;br&gt;
    throw new Error(&lt;code&gt;role "${roleName}" not found at scope "${scope}"&lt;/code&gt;);&lt;br&gt;
  }&lt;br&gt;
  if (roles.length &amp;gt; 1) {&lt;br&gt;
    throw new Error(&lt;br&gt;
      &lt;code&gt;too many roles "${roleName}" found at scope "${scope}". Found: ${roles.length}&lt;/code&gt;&lt;br&gt;
    );&lt;br&gt;
  }&lt;br&gt;
  const role = roles[0];&lt;br&gt;
  return role.id!;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// create an azure AD application&lt;br&gt;
const adApp = new azuread.Application("gha", {&lt;br&gt;
  displayName: "githubActions",&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// create a service principal&lt;br&gt;
const adSp = new azuread.ServicePrincipal(&lt;br&gt;
  "ghaSp",&lt;br&gt;
  { applicationId: adApp.applicationId },&lt;br&gt;
  { parent: adApp }&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;// mandatory SP password&lt;br&gt;
const adSpPassword = new azuread.ServicePrincipalPassword(&lt;br&gt;
  "aksSpPassword",&lt;br&gt;
  {&lt;br&gt;
    servicePrincipalId: adSp.id,&lt;br&gt;
    endDate: "2099-01-01T00:00:00Z",&lt;br&gt;
  },&lt;br&gt;
  { parent: adSp }&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;/*&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the magic. We set the subject to the repo we're running from&lt;/li&gt;
&lt;li&gt;Also need to ensure your AD Application is the one where access is defined
*/
new azuread.ApplicationFederatedIdentityCredential(
"gha",
{
audiences: ["api://AzureADTokenExchange"],
subject: "repo:jaxxstorm/secure-cloud-access:ref:refs/heads/main", // this can be any ref
issuer: "&lt;a href="https://token.actions.githubusercontent.com"&gt;https://token.actions.githubusercontent.com&lt;/a&gt;",
applicationObjectId: adApp.objectId,
displayName: "github-actions",
},
{ parent: adApp }
);&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// retrieve the current tenant and subscription&lt;br&gt;
const subInfo = authorization.getClientConfig();&lt;/p&gt;

&lt;p&gt;subInfo.then((info) =&amp;gt; {&lt;/p&gt;

&lt;p&gt;// define some github actions secrets so your AZ login is correct&lt;br&gt;
  new github.ActionsSecret("tenantId", {&lt;br&gt;
    repository: "secure-cloud-access",&lt;br&gt;
    secretName: "AZURE_TENANT_ID",&lt;br&gt;
    plaintextValue: info.tenantId,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;new github.ActionsSecret("subscriptionId", {&lt;br&gt;
    repository: "secure-cloud-access",&lt;br&gt;
    secretName: "AZURE_SUBSCRIPTION_ID",&lt;br&gt;
    plaintextValue: info.subscriptionId,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;/* define a role assignment so we have permissions on the subscription&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use the helper to get the role by name, but you can of course define it explicitly
*/
new authorization.RoleAssignment("readOnly", {
principalId: adSp.id,
principalType: authorization.PrincipalType.ServicePrincipal,
scope: pulumi.interpolate&lt;code&gt;/subscriptions/${info.subscriptionId}&lt;/code&gt;,
roleDefinitionId: getRoleIdByName("Reader"),
});
});&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// finally, we set the client id to be the application we created&lt;br&gt;
new github.ActionsSecret("clientId", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "AZURE_CLIENT_ID",&lt;br&gt;
  plaintextValue: adApp.applicationId,&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run your Pulumi program and define all your infrastructure, then we can define our workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the GitHub Actions workflow
&lt;/h3&gt;

&lt;p&gt;Our GitHub actions workflow will use the secrets we defined to know how to authenticate. It looks a little bit like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`yaml&lt;br&gt;
name: Run Azure Login with OpenID Connect&lt;br&gt;
on: [push]&lt;/p&gt;

&lt;p&gt;permissions:&lt;br&gt;
  id-token: write&lt;br&gt;
  contents: read&lt;/p&gt;

&lt;p&gt;jobs: &lt;br&gt;
  CheckAccess:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
    - name: 'Az CLI login'&lt;br&gt;
      uses: azure/login@v1&lt;br&gt;
      with:&lt;br&gt;
        client-id: ${{ secrets.AZURE_CLIENT_ID }}&lt;br&gt;
        tenant-id: ${{ secrets.AZURE_TENANT_ID }}&lt;br&gt;
        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: 'Run Azure CLI commands'
  run: |
      az account show
      az group list
      pwd 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Check all this in, and watch the magic as GitHub Actions is now authenticated against Azure!&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Cloud
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The complete code for this example can be found &lt;a href="https://github.com/jaxxstorm/secure-cloud-access/tree/main/gcp"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Google Cloud supports OIDC authentication using workflow providers. We'll use the &lt;code&gt;@pulumi/gcp&lt;/code&gt; and &lt;code&gt;@pulumi/google-native&lt;/code&gt; to achieve our goals here, so make sure the following packages are installed in your Pulumi program:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;yaml&lt;br&gt;
npm install @pulumi/gcp @pulumi/google-native&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the Service Account
&lt;/h2&gt;

&lt;p&gt;We'll need a GCP service account to for GitHub actions to use. We'll assign the service account the &lt;code&gt;viewer&lt;/code&gt; role:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
const serviceAccount = new google.iam.v1.ServiceAccount(name, {&lt;br&gt;
    accountId: "github-actions"&lt;br&gt;
})&lt;/p&gt;

&lt;p&gt;new gcp.projects.IAMMember("github-actions", {&lt;br&gt;
    role: "roles/viewer",&lt;br&gt;
    member: pulumi.interpolate&lt;code&gt;serviceAccount:${serviceAccount.email}&lt;/code&gt;&lt;br&gt;
})&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a WorkloadIdentityPool
&lt;/h3&gt;

&lt;p&gt;Now we'll define a workload identity pool for GitHub actions to use&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
const identityPool = new gcp.iam.WorkloadIdentityPool("github-actions", {&lt;br&gt;
  disabled: false,&lt;br&gt;
  workloadIdentityPoolId:&lt;/code&gt;github-actions&lt;code&gt;,&lt;br&gt;
});&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a WorkloadIdentityPoolProvider
&lt;/h2&gt;

&lt;p&gt;We'll now need to define a provider for this workload identity pool. The mappings section is important, here is where we map Google OIDC subjects to the OIDC token objects. The following works pretty well:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
const identityPoolProvider = new gcp.iam.WorkloadIdentityPoolProvider(&lt;br&gt;
  "github-actions",&lt;br&gt;
  {&lt;br&gt;
    workloadIdentityPoolId: identityPool.workloadIdentityPoolId,&lt;br&gt;
    workloadIdentityPoolProviderId: "github-actions",&lt;br&gt;
    oidc: {&lt;br&gt;
      issuerUri: "https://token.actions.githubusercontent.com",&lt;br&gt;
    },&lt;br&gt;
    attributeMapping: {&lt;br&gt;
      "google.subject": "assertion.sub",&lt;br&gt;
      "attribute.actor": "assertion.actor",&lt;br&gt;
      "attribute.repository": "assertion.repository",&lt;br&gt;
    },&lt;br&gt;
  }&lt;br&gt;
);&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Assign the workload identity permission
&lt;/h2&gt;

&lt;p&gt;Now we've defined the workload identity and provider, we need to allow our earlier defined service account to use these new resources:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
new gcp.serviceaccount.IAMMember("repository", {&lt;br&gt;
    serviceAccountId: serviceAccount.name,&lt;br&gt;
    role: "roles/iam.workloadIdentityUser",&lt;br&gt;
    member: pulumi.interpolate&lt;/code&gt;principalSet://iam.googleapis.com/${identityPool.name}/attribute.repository/jaxxstorm/secure-cloud-access&lt;code&gt;&lt;br&gt;
})&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice here that we interpolate the name of the identity pool, and also the name of the repository that we want to access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the GitHub actions secrets
&lt;/h3&gt;

&lt;p&gt;Now we'll store some important information in GitHub secrets so we don't have to hardcode them in our workflow:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
new github.ActionsSecret("identityProvider", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "WORKLOAD_IDENTITY_PROVIDER",&lt;br&gt;
  plaintextValue: identityPoolProvider.name,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;new github.ActionsSecret("subscriptionId", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "SERVICE_ACCOUNT_EMAIL",&lt;br&gt;
  plaintextValue: serviceAccount.email,&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We're storing the identity pool provider name, and the service account we created's email address as actions.&lt;/p&gt;

&lt;p&gt;Your complete Pulumi program should look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`typescript&lt;br&gt;
import * as pulumi from "@pulumi/pulumi";&lt;br&gt;
import * as gcp from "@pulumi/gcp";&lt;br&gt;
import * as google from "@pulumi/google-native";&lt;br&gt;
import * as github from "@pulumi/github";&lt;/p&gt;

&lt;p&gt;const name = "github-actions";&lt;/p&gt;

&lt;p&gt;const serviceAccount = new google.iam.v1.ServiceAccount(name, {&lt;br&gt;
  accountId: "github-actions",&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;new gcp.projects.IAMMember("github-actions", {&lt;br&gt;
  role: "roles/viewer",&lt;br&gt;
  member: pulumi.interpolate&lt;code&gt;serviceAccount:${serviceAccount.email}&lt;/code&gt;,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const identityPool = new gcp.iam.WorkloadIdentityPool("github-actions", {&lt;br&gt;
  disabled: false,&lt;br&gt;
  workloadIdentityPoolId: &lt;code&gt;${name}-4&lt;/code&gt;,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;const identityPoolProvider = new gcp.iam.WorkloadIdentityPoolProvider(&lt;br&gt;
  "github-actions",&lt;br&gt;
  {&lt;br&gt;
    workloadIdentityPoolId: identityPool.workloadIdentityPoolId,&lt;br&gt;
    workloadIdentityPoolProviderId: &lt;code&gt;${name}&lt;/code&gt;,&lt;br&gt;
    oidc: {&lt;br&gt;
      issuerUri: "&lt;a href="https://token.actions.githubusercontent.com"&gt;https://token.actions.githubusercontent.com&lt;/a&gt;",&lt;br&gt;
    },&lt;br&gt;
    attributeMapping: {&lt;br&gt;
      "google.subject": "assertion.sub",&lt;br&gt;
      "attribute.actor": "assertion.actor",&lt;br&gt;
      "attribute.repository": "assertion.repository",&lt;br&gt;
    },&lt;br&gt;
  }&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;new gcp.serviceaccount.IAMMember("repository", {&lt;br&gt;
  serviceAccountId: serviceAccount.name,&lt;br&gt;
  role: "roles/iam.workloadIdentityUser",&lt;br&gt;
  member: pulumi.interpolate&lt;code&gt;principalSet://iam.googleapis.com/${identityPool.name}/attribute.repository/jaxxstorm/secure-cloud-access&lt;/code&gt;,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;new github.ActionsSecret("identityProvider", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "WORKLOAD_IDENTITY_PROVIDER",&lt;br&gt;
  plaintextValue: identityPoolProvider.name,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;new github.ActionsSecret("subscriptionId", {&lt;br&gt;
  repository: "secure-cloud-access",&lt;br&gt;
  secretName: "SERVICE_ACCOUNT_EMAIL",&lt;br&gt;
  plaintextValue: serviceAccount.email,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;export const workloadIdentityProviderUrl = identityPoolProvider.name;&lt;br&gt;
export const serviceAccountEmail = serviceAccount.email;&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run your Pulumi program, created the needed resources and then we can define our workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the workflow
&lt;/h3&gt;

&lt;p&gt;Now we've configured all the access we need, we can define a workflow to check our access:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`yaml&lt;br&gt;
name: List services in GCP&lt;br&gt;
on:&lt;br&gt;
  push&lt;/p&gt;

&lt;p&gt;permissions:&lt;br&gt;
  id-token: write&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  Get_OIDC_ID_token:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
    - id: 'auth'&lt;br&gt;
      name: 'Authenticate to GCP'&lt;br&gt;
      uses: 'google-github-actions/&lt;a href="mailto:auth@v0.3.1"&gt;auth@v0.3.1&lt;/a&gt;'&lt;br&gt;
      with:&lt;br&gt;
          create_credentials_file: 'true'&lt;br&gt;
          workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}&lt;br&gt;
          service_account: ${{ secrets.SERVICE_ACCOUNT_EMAIL }}&lt;br&gt;
    - id: 'gcloud'&lt;br&gt;
      name: 'gcloud'&lt;br&gt;
      run: |-&lt;br&gt;
        gcloud auth login --brief --cred-file="${{ steps.auth.outputs.credentials_file_path }}" --project briggs-237615&lt;br&gt;
        gcloud auth list&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We're using the &lt;code&gt;auth&lt;/code&gt; action, creating a credentials file and then verifying we're authenticated.&lt;/p&gt;

&lt;p&gt;Check all this in, and watch in awe as your GitHub action runs with GCP access without any hardcoded credentials!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;This blog post guides you through accessing the 3 major cloud providers with GitHub Actions without specifying hardcoded credentials. It's my hope that more CI/CD providers will offer this support soon, as well as other awesome cloud providers. &lt;/p&gt;

</description>
      <category>aws</category>
      <category>azure</category>
      <category>github</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>VB.NET - The Future of Infrastructure as Cloud</title>
      <dc:creator>Lee Briggs</dc:creator>
      <pubDate>Thu, 17 Dec 2020 06:35:27 +0000</pubDate>
      <link>https://forem.com/jaxxstorm/vb-net-the-future-of-infrastructure-as-cloud-36i2</link>
      <guid>https://forem.com/jaxxstorm/vb-net-the-future-of-infrastructure-as-cloud-36i2</guid>
      <description>&lt;p&gt;I can remember the exact moment I first realized I &lt;em&gt;didn't&lt;/em&gt; want to be a software developer. It was May 2009, and I was desperately trying to finish my final University project for the module "object-orientated Programming."&lt;/p&gt;

&lt;p&gt;My module tutor had tasked us with creating an application of our choosing in VB.NET. I can unapologetically say I didn't enjoy the process. I preferred focusing on systems, understanding how things fit together. I was spending my days getting stuck in the minutiae of fixing bugs in my code.&lt;/p&gt;

&lt;p&gt;My career progression from there followed a fairly traditional "System Administrator" path. I managed some Linux servers, the number grew, and I went headfirst into configuration management. As the DevOps movement came around, I embraced Kubernetes early and spent several years using cutting edge technologies in exciting ways. Despite telling myself in 2009 I didn't want to be a software engineer, in 2020 I got my first "software engineer" title here at Pulumi, and now I'm lucky enough to spend my time writing code to program the cloud. &lt;/p&gt;

&lt;h1&gt;
  
  
  Revisiting the Past
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://pulumi.com"&gt;Pulumi&lt;/a&gt; is an exciting product, but what's incredible to see working here is how the platform scales. All of the &lt;a href="https://www.pulumi.com/docs/intro/cloud-providers/"&gt;Pulumi provider&lt;/a&gt; SDKs get programmatically generated, which means that once support for a language gets added, all of the available providers will get that SDK generated. Adding a new language has significant engineering work up front, of course, but ongoing maintenance burden drops over time rather than increases. &lt;/p&gt;

&lt;p&gt;In November 2019, Pulumi added support for the &lt;a href="https://www.pulumi.com/blog/pulumi-dotnet-core/"&gt;.NET core languages&lt;/a&gt;. We've seen significant adoption from the .NET community, and if you take a look at our &lt;a href="https://github.com/pulumi/examples"&gt;examples repo&lt;/a&gt;, you'll see dozens of examples of creating cloud infrastructure with &lt;a href="https://en.wikipedia.org/wiki/C_Sharp_(programming_language)"&gt;C#&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/F_Sharp_(programming_language)"&gt;F#&lt;/a&gt;, the flagship languages of the .NET framework.&lt;/p&gt;

&lt;p&gt;Of course, .NET core languages also include my trusty old friend VB.NET. I haven't written a single line of VB.Net since I handed in my university project in 2009, and I have a vague recollection that I told myself that I never would again. &lt;/p&gt;

&lt;p&gt;However, this is 2020. So I asked myself, what is the very last thing I would want to create with Pulumi in VB.NET. The answer is obvious, of course - Kubernetes resources.&lt;/p&gt;

&lt;h1&gt;
  
  
  The old and the new
&lt;/h1&gt;

&lt;p&gt;In many ways, VB.NET and Kubernetes couldn't be further apart. VB.NET, of old Microsoft - a language of "&lt;a href="https://en.wiktionary.org/wiki/code-behind"&gt;code-behind&lt;/a&gt;", written for the era of monolithic desktop apps and windows forms and &lt;a href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt;, a complex distributed system written to be &lt;a href="https://www.cncf.io/"&gt;cloud-native&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;With nostalgia in mind, I asked in our internal slack just how good our VB.NET support was. I quickly got a helpful answer from one of our &lt;a href="https://twitter.com/mikhailshilkov"&gt;resident .NET experts&lt;/a&gt; which can when summarized is along the lines of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It should work, but...why?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer to that question in my mind was of course, because it's 2020.&lt;br&gt;
Our &lt;a href="https://github.com/pulumi/templates/"&gt;Pulumi templates&lt;/a&gt; (used to bootstrap new Pulumi projects) helpfully already had &lt;a href="https://github.com/pulumi/templates/tree/master/aws-visualbasic"&gt;VisualBasic examples&lt;/a&gt;. I imagine they're lonely, because I couldn't find a single record of them ever getting used.&lt;/p&gt;

&lt;p&gt;Bootstrapping my new cloud VB.NET project, I could feel the old familiarity of hating being a software developer return to me. Shaking off the innate feeling, I set about doing the Pulumi equivalent of "Hello, world" - creating an S3 bucket in AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Aws.S3&lt;/span&gt;

&lt;span class="k"&gt;Class&lt;/span&gt; &lt;span class="nc"&gt;MyStack&lt;/span&gt;
    &lt;span class="k"&gt;Inherits&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;

    &lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt; &lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;' Create an AWS resource (S3 Bucket)&lt;/span&gt;
        &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&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="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-bucket"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;' Export the name of the bucket&lt;/span&gt;
        &lt;span class="k"&gt;Me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BucketName&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="n"&gt;Id&lt;/span&gt;
    &lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Property&lt;/span&gt; &lt;span class="nf"&gt;BucketName&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Of&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I sort of hoped it would fail. VB.Net is 19 years old. Surely it has no place provisioning AWS resources?&lt;/p&gt;

&lt;p&gt;I had some difficulty familiarizing myself with the syntax, but after yelling at my IDE a couple times, I finally managed to get my S3 bucket created.&lt;/p&gt;

&lt;p&gt;At this point, I decided to antagonize the internet and rehash a meme from earlier in the year:&lt;/p&gt;

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

&lt;p&gt;The responses I received weren't surprising:&lt;/p&gt;

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

&lt;p&gt;Undeterred, I went for broke. Adding the Kubernetes provider and building out the required resources to deploy to an already provisioned EKS cluster was easier than I expected. I actually started to feel like the syntax of VB.net felt oddly suited to the task. It looks.... good?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Kubernetes.Apps.V1&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Kubernetes.Core.V1&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Kubernetes.Types.Inputs.Apps.V1&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Kubernetes.Types.Inputs.Core.V1&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;Pulumi.Kubernetes.Types.Inputs.Meta.V1&lt;/span&gt;

&lt;span class="k"&gt;Class&lt;/span&gt; &lt;span class="nc"&gt;NginxStack&lt;/span&gt; 
  &lt;span class="k"&gt;Inherits&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;

  &lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt; &lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    

    &lt;span class="c1"&gt;' an input map of labels&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;InputMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Of&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;From&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nginx"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

    &lt;span class="c1"&gt;' define the deployment spec&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;containerPortArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;ContainerPortArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainerPortValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;containerArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;ContainerArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nginx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nginx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;containerPortArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;podSpecArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PodSpecArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;containerArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PodTemplateSpecArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;podSpecArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;ObjectMetaArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;labelSelectorArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;LabelSelectorArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MatchLabels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;deploymentSpecArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;DeploymentSpecArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Replicas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labelSelectorArgs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;' create the actual deployment&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;deployment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&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;Kubernetes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Apps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nginx"&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;DeploymentArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deploymentSpecArgs&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;' define the service spec&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;servicePortArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;ServicePortArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TargetPort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;serviceSpecArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;ServiceSpecArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;servicePortArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"LoadBalancer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;objectMetaArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;ObjectMetaArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nginx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;' create a service&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;New&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;Kubernetes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;V1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nginx"&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;ServiceArgs&lt;/span&gt; &lt;span class="k"&gt;With&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectMetaArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceSpecArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&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="k"&gt;Function&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;If&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadBalancer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ingress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadBalancer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ingress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Hostname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Property&lt;/span&gt; &lt;span class="nf"&gt;Address&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Of&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I shared what I'd done with some of the Slack communities I frequent:&lt;/p&gt;

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

&lt;p&gt;I knew what I was doing was a little controversial, but I wasn't ready for this:&lt;/p&gt;

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

&lt;p&gt;I felt defensive all of a sudden. Was I starting to feel defensive of VB.NET?&lt;/p&gt;

&lt;p&gt;Luckily, &lt;a href="https://twitter.com/funcOfJoe/"&gt;the CEO of Pulumi&lt;/a&gt;, a man who knows a thing or two about programming languages, saw my example code and saw the beauty of what I'd done:&lt;/p&gt;

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

&lt;p&gt;Not everyone saw the inner-beauty:&lt;/p&gt;

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

&lt;p&gt;You can see this incredible collaboration of old and new here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/Uq17k64ym5x4tl2aLmTFRg3i0"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GRf2W6bO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://asciinema.org/a/Uq17k64ym5x4tl2aLmTFRg3i0.svg" alt="asciicast" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full code, if you'd like to see it, is &lt;a href="https://github.com/jaxxstorm/eks-vb-net"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd ask me what I learned from this experience, I'd be honest and say - nothing. Do I really believe VB.NET is the future of cloud engineering? No. It's not. I'm unlikely to recommend to Pulumi customers that they break out Visual Studio and write some VB.NET, but we know the options is there if they ever want to use it.&lt;/p&gt;

&lt;p&gt;I've gone on record before to talk about how much I &lt;a href="//2019-02-07-why-are-we-templating-yaml.md"&gt;loathe templating YAML&lt;/a&gt; and I love the expressability Pulumi gives me with a familiar programming languages. Even though I hated VB.NET in university, I would still prefer to express my cloud resources with a 19 year old programming language over a configuration language with template replacement.&lt;/p&gt;

</description>
      <category>cloudnative</category>
      <category>cloud</category>
      <category>programming</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
