<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris Phillipson</title>
    <description>The latest articles on DEV Community by Chris Phillipson (@pacphi).</description>
    <link>https://dev.to/pacphi</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%2F1016393%2Fd257dca8-902c-4a4a-8c1d-88c235df62bd.png</url>
      <title>DEV Community: Chris Phillipson</title>
      <link>https://dev.to/pacphi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pacphi"/>
    <language>en</language>
    <item>
      <title>Crossplane and Terraform</title>
      <dc:creator>Chris Phillipson</dc:creator>
      <pubDate>Sun, 29 Jan 2023 00:56:37 +0000</pubDate>
      <link>https://dev.to/pacphi/crossplane-and-terraform-2k7p</link>
      <guid>https://dev.to/pacphi/crossplane-and-terraform-2k7p</guid>
      <description>&lt;p&gt;Today, I asked ChatGPT a few questions so I might understand whether it would be advantageous to use Crossplane with Terraform.&lt;/p&gt;

&lt;p&gt;Here's the exchange...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please compare and contrast the use of Terraform and Crossplane when used to create and destroy managed public cloud resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Terraform and Crossplane are both Infrastructure as Code (IAC) tools that can be used to create and destroy managed public cloud resources. However, they have some key differences in their design and use-cases.&lt;/p&gt;

&lt;p&gt;Terraform is a tool developed by HashiCorp that allows users to define and provision infrastructure resources using HashiCorp Configuration Language (HCL). It supports a wide range of cloud providers, including AWS, Azure, and Google Cloud, as well as on-premises and other infrastructure. Terraform is focused on provisioning and managing resources on a specific cloud provider.&lt;/p&gt;

&lt;p&gt;Crossplane, on the other hand, is an open-source control plane for multi-cloud infrastructure that allows users to define and provision infrastructure resources using Kubernetes Custom Resource Definitions (CRDs). It supports multiple cloud providers, but also provides an abstraction layer over them, allowing users to provision resources using a consistent API regardless of the underlying cloud provider.&lt;/p&gt;

&lt;p&gt;In summary, Terraform is a cloud-agnostic tool that helps in provisioning and managing resources on a specific cloud provider, whereas Crossplane is an open-source control plane for multi-cloud infrastructure that provides a consistent API for provisioning resources across multiple cloud providers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please contrast how Crossplane and Terraform manage state. What happens when Terraform state is lost? What happens to managed resources when the cluster Crossplane is deployed into is destroyed?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both Terraform and Crossplane manage the state of the infrastructure they are provisioning, but they do it in different ways.&lt;/p&gt;

&lt;p&gt;Terraform uses a local or remote state file to store the current state of the infrastructure it is managing. This state file is used to ensure that the infrastructure is in the desired state after a Terraform apply command is executed. The state file can be stored locally or remotely, such as in a cloud object storage service like S3. If the state file is lost or becomes out of sync, Terraform can become unable to determine the correct state of the infrastructure, which can lead to unexpected changes or failures when applying Terraform configurations. To mitigate this issue, Terraform provides a number of state management features, such as state locking and state versioning, to help ensure the state file remains accurate.&lt;/p&gt;

&lt;p&gt;Crossplane, on the other hand, stores the state of the infrastructure it is managing in the Kubernetes cluster where it is deployed. This state is stored in Kubernetes Custom Resource Definitions (CRDs) and can be managed using the Kubernetes API. If the Kubernetes cluster where Crossplane is deployed is destroyed, the state of the infrastructure managed by Crossplane will also be lost. However, Crossplane also has built-in recovery mechanisms to ensure that the state of the infrastructure is restored when the cluster is recreated.&lt;/p&gt;

&lt;p&gt;In summary, Terraform uses a local or remote state file to manage the state of the infrastructure it provisions, whereas Crossplane stores the state of the infrastructure it provisions in the Kubernetes cluster where it is deployed. If the state file is lost, Terraform can become unable to determine the correct state of the infrastructure, whereas if the cluster Crossplane is deployed into is destroyed, the state of the infrastructure managed by Crossplane will also be lost, but it has built-in recovery mechanisms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can Crossplane detect whether a resource has already been created without state? Likewise, can Terraform detect whether a resource has been created without state?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both Crossplane and Terraform can detect whether a resource has already been created without state, but they do it in different ways.&lt;/p&gt;

&lt;p&gt;Crossplane can detect whether a resource has already been created without state by querying the Kubernetes API for the existence of the resource. Because Crossplane stores the state of the infrastructure it is managing in the Kubernetes cluster where it is deployed, it can use the Kubernetes API to check if the resource already exists. If the resource already exists, Crossplane can retrieve its current state and compare it to the desired state defined in the Crossplane resource definition.&lt;/p&gt;

&lt;p&gt;Terraform can also detect whether a resource has already been created without state by querying the cloud provider's API for the existence of the resource. Terraform can also check if the resource already exists in the state file. If the resource already exists, Terraform can retrieve its current state and compare it to the desired state defined in the Terraform configuration file.&lt;/p&gt;

&lt;p&gt;In summary, Crossplane can detect whether a resource has already been created without state by querying the Kubernetes API, whereas Terraform can detect whether a resource has already been created without state by querying the cloud provider's API and state file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Can you show me how to configure Terraform to update the state of an existing resource when that resource is not currently managed by Terraform?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, you can configure Terraform to update the state of an existing resource when that resource is not currently managed by Terraform. This can be done using the &lt;code&gt;terraform import&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;Here is an example of how to use the &lt;code&gt;terraform import&lt;/code&gt; command to update the state for an existing AWS S3 bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# First, determine the resource type and the unique identifier for the resource you want to import
&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt; &lt;span class="c1"&gt;# This command will show all the buckets in your AWS account
# Let's say you want to import an existing bucket named "my-bucket"
# Next, use the terraform import command to import the existing resource into Terraform's state
&lt;/span&gt;&lt;span class="n"&gt;terraform&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;aws_s3_bucket.my_bucket&lt;/span&gt; &lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The above command imports the existing S3 bucket named "my-bucket" into Terraform's state. This will allow Terraform to know about the existence of the resource and treat it as a managed resource going forward.&lt;br&gt;&lt;br&gt;
It is important to note that running the import command will not change the state of the resource, it just makes Terraform aware of the resource, so you will need to run &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt; after running the import command to make sure that the resource state matches with the state defined in the Terraform configuration file.&lt;/p&gt;

&lt;p&gt;Also, You may need to check if the provider you are using is already defined in your Terraform configuration file. If not, you need to define it before running the import command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Is it more advantageous to use Crossplane versus Terraform for the reason that Crossplane will continuously reconcile and prevent configuration drift?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Crossplane and Terraform have different advantages and use cases.&lt;br&gt;&lt;br&gt;
Crossplane is designed to provide a unified interface for provisioning and managing multiple cloud providers and on-premises resources. It uses Kubernetes Custom Resource Definitions (CRDs) to define the desired state of the infrastructure and automatically reconciles the state of the infrastructure with the desired state defined in the CRDs. This allows for continuous monitoring and correction of any drift that may occur between the actual state of the infrastructure and the desired state. This makes Crossplane particularly useful for organizations that are looking to provision and manage resources across multiple clouds and on-premises environments using a consistent set of APIs and abstractions.&lt;/p&gt;

&lt;p&gt;On the other hand, Terraform is a tool specifically designed for provisioning and managing infrastructure. It uses a configuration language called HashiCorp Configuration Language (HCL) to define the desired state of the infrastructure. Terraform can provision resources in multiple cloud providers and on-premises environments as well, but it doesn't have built-in mechanisms to continuously reconcile and prevent configuration drift like Crossplane. However, it can detect drift between the current state and the desired state and generates a plan that shows what changes are needed to bring the infrastructure in the desired state.&lt;/p&gt;

&lt;p&gt;In summary, Crossplane provides continuous monitoring and correction of any drift that may occur between the actual state of the infrastructure and the desired state, making it particularly useful for organizations looking to provision and manage resources across multiple clouds and on-premises environments using a consistent set of APIs and abstractions. Terraform is a powerful tool for provisioning and managing infrastructure in multiple cloud providers and on-premises environments, but it does not have built-in mechanisms for continuous drift reconciliation like Crossplane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Are you able to articulate the benefits of using Crossplane with its Terraform provider?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using Crossplane with its Terraform provider can provide several benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Consistent API for provisioning and managing resources: Crossplane provides a consistent API for provisioning and managing resources across multiple cloud providers and on-premises environments, regardless of whether the resources are managed using Terraform or other provisioning tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automated drift detection and correction: By using Crossplane's built-in mechanisms for continuous monitoring and correction of any drift that may occur between the actual state of the infrastructure and the desired state, you can ensure that your resources are always in the desired state, even if they were initially provisioned using Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Centralized management: With Crossplane, you can centrally manage and provision resources across multiple cloud providers and on-premises environments, which can help to simplify the management and organization of your infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-cloud resource provisioning: Crossplane's Terraform provider allows you to provision resources across multiple clouds in a consistent way, meaning that you can use the same Terraform configuration to provision resources in AWS, Azure, GCP, and other cloud providers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reusability: With Crossplane, you can reuse the same Terraform modules and providers that you use for provisioning resources in the public cloud, in your on-premises environments as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compliance and Governance: Crossplane can enforce compliance and governance policies by automating the detection of drift and correcting it, across all the resources provisioned by Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In summary, using Crossplane with its Terraform provider can help organizations to have a consistent, centralized and automated way to provision, manage and monitor resources in multiple cloud providers and on-premises environments, with the added benefits of compliance and governance enforcement.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Yelling at my laptop - Part 1</title>
      <dc:creator>Chris Phillipson</dc:creator>
      <pubDate>Fri, 27 Jan 2023 07:00:41 +0000</pubDate>
      <link>https://dev.to/pacphi/yelling-at-my-laptop-part-1-4ec0</link>
      <guid>https://dev.to/pacphi/yelling-at-my-laptop-part-1-4ec0</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;This post chronicles my attempt at installing a commercial, secure software supply-chain platform on top of a public cloud provider's managed &lt;a href="https://stackify.com/kubernetes-service/" rel="noopener noreferrer"&gt;Kubernetes-as-service&lt;/a&gt; offering by not explicitly following public documentation.&lt;/p&gt;

&lt;p&gt;First, let me share a personal bias. I don't have the best recall. Being asked to execute a complex task that I may have completed a few weeks or months ago heightens my anxiety. Particularly, if I'm under a time constraint. So, if I know I'm going to have to do something repeatedly, I'm going to automate it. I do this to reduce toil and avoid potential mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the choice of a technology stack
&lt;/h2&gt;

&lt;p&gt;Every DIY developer I know forms opinions on the choice of tech stack to aid in value delivery. They may or may not have the authority and autonomy to exercise those opinions in an enterprise setting, where the choice of toolset, frameworks and automation may be dictated by any number of reasons (e.g., feasibility, contractual, community, financial, regulatory). Therefore, when I settle on a technology stack to accomplish a task, I want to think about how I'd position its applicability in that context.&lt;/p&gt;

&lt;p&gt;To accomplish the task of provisioning public cloud resources, I chose &lt;a href="https://www.terraform.io/use-cases/infrastructure-as-code" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;. It ticks several boxes, not the least that I have several years of experience with it. I need to be able to create and destroy cloud environments hosting a footprint of multiple Kubernetes clusters. At a minimum that consists of a virtual network and subnets, a container registry, one or more clusters, one or more DNS zones, blob storage, and a secrets manager instance.&lt;/p&gt;

&lt;p&gt;To work with a cluster, of course, I'll need the &lt;a href="https://kubernetes.io/docs/reference/kubectl/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt; client. To make my life easier I'll install a few &lt;a href="https://krew.sigs.k8s.io/docs/user-guide/quickstart/" rel="noopener noreferrer"&gt;krew&lt;/a&gt; plugins like &lt;a href="https://github.com/weibeld/kubectl-ctx" rel="noopener noreferrer"&gt;ctx&lt;/a&gt;, &lt;a href="https://github.com/micnncim/kubectl-reap" rel="noopener noreferrer"&gt;reap&lt;/a&gt;, and &lt;a href="https://github.com/elsesiy/kubectl-view-secret" rel="noopener noreferrer"&gt;view-secret&lt;/a&gt;. To build, tag, pull and push container images for test purposes, I'll use a combination of &lt;a href="https://docs.docker.com/get-started/" rel="noopener noreferrer"&gt;docker&lt;/a&gt; and &lt;a href="https://buildpacks.io/docs/tools/pack/" rel="noopener noreferrer"&gt;pack&lt;/a&gt; - in anger. Lastly, I'll rely on a complement of other sharp tools, like &lt;a href="https://github.com/mikefarah/yq" rel="noopener noreferrer"&gt;yq&lt;/a&gt;, &lt;a href="https://carvel.dev/kapp/" rel="noopener noreferrer"&gt;kapp&lt;/a&gt;, and &lt;a href="https://carvel.dev/ytt/" rel="noopener noreferrer"&gt;ytt&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provisioning cloud resources
&lt;/h2&gt;

&lt;p&gt;With time I've acquired and developed skills with &lt;a href="https://github.com/hashicorp/hcl" rel="noopener noreferrer"&gt;HCL&lt;/a&gt;, authoring and consuming Terraform modules. I've often found it to be the case that when I want to stitch something together myself someone else has authored a coarse-grained module on Github or the Terraform Registry that's designed in such a way that I can easily leverage and/or extend it. (It's particularly gratifying when I pick a module where large communities have evolved to maintain and consume it).&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an Artifact Registry instance
&lt;/h2&gt;

&lt;p&gt;With that bit of background, let's focus on the creation of a &lt;a href="https://cloud.google.com/artifact-registry/docs/overview" rel="noopener noreferrer"&gt;Google Artifact Registry&lt;/a&gt;. Artifact registries allow you to host many types of artifacts; for my purposes, I needed to create one to host container images. The first thing you need to grok (which I failed to understand early on, wasting huge amounts of time) is how artifacts are organized for storage and retrieval. The public documentation available speaks to how you &lt;a href="https://cloud.google.com/artifact-registry/docs/repositories/create-repos" rel="noopener noreferrer"&gt;create&lt;/a&gt;, authenticate, then &lt;a href="https://cloud.google.com/artifact-registry/docs/docker/pushing-and-pulling" rel="noopener noreferrer"&gt;push and pull&lt;/a&gt; container images.&lt;/p&gt;

&lt;p&gt;So, after glancing at that I set about designing a Terraform module to lifecycle-manage an Artifact registry instance and a repository within it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;main.tf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"external"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"env"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/env.sh"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google_kms_key_ring"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;var.keyring&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;var.location&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google_kms_crypto_key"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terraform"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;key_ring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data.google_kms_key_ring.terraform.id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google_artifact_registry_repository"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"instance"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;for_each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;toset(var.repository_names)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;var.location&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;kms_key_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data.google_kms_crypto_key.terraform.id&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;repository_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;each.key&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OCI image repository"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DOCKER"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;env.sh&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c"&gt;# env.sh&lt;/span&gt;

&lt;span class="c"&gt;# Change the contents of this output to get the environment variables&lt;/span&gt;
&lt;span class="c"&gt;# of interest. The output must be valid JSON, with strings for both&lt;/span&gt;
&lt;span class="c"&gt;# keys and values.&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "google_service_account_key": "&lt;/span&gt;&lt;span class="nv"&gt;$GOOGLE_SERVICE_ACCOUNT_KEY&lt;/span&gt;&lt;span class="sh"&gt;"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;variables.tf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A valid Google project identifier"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;sensitive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repository_names"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;list(string)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Specifies the names of repositories that will be created within Google Cloud Artifact Registry"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tanzu"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"location"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Google Cloud Artifact Registry Repository and Google Cloud KMS keyring locations"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;validation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;condition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;contains(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"us-west1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-west2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-central1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"europe-north1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"europe-west1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"europe-southwest1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asia-east1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asia-south1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asia-northeast3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"australia-southeast2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;var.location)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;error_message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Valid values for Google Cloud Artifact Registry locations are (us-west1, us-west2, us-east1, us-central1, europe-north1, europe-west1, europe-southwest1, asia-east1, asia-south1, asia-northeast3, australia-southeast2)."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-west2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyring"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;providers.tf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;var.project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;versions.tf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;terraform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;required_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;= 0.14.0"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;required_providers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;google&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hashicorp/google"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;= 4.33.0"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;outputs.tf&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin_username"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"_json_key_base64"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin_password"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The base64-encoded password associated with the Container Registry admin account"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;base&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="err"&gt;encode(data.external.env.result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"google_service_account_key"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;sensitive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"endpoint"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${var.location}-docker.pkg.dev"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;terraform.tfvars&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REPLACE_ME"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;repository_names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tanzu"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-west2"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;keyring&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REPLACE_ME"&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;create-artifact-registry.sh&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

terraform init &lt;span class="nt"&gt;-upgrade&lt;/span&gt;
terraform validate
terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt; terraform.plan
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt; &lt;span class="nt"&gt;-state&lt;/span&gt; terraform.tfstate terraform.plan

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;destroy-artifact-registry.sh&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

terraform destroy &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-Rf&lt;/span&gt; .terraform .terraform.lock.hcl terraform.tfstate terraform.tfstate.backup terraform.log terraform.plan

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

&lt;/div&gt;



&lt;p&gt;Note the use of scripts. I don't want to have to think too hard about the sequence of Terraform commands to issue to create and destroy this cloud resource.&lt;/p&gt;

&lt;p&gt;Also, importantly, we're only creating one repository named &lt;strong&gt;tanzu&lt;/strong&gt; within the registry instance.&lt;/p&gt;

&lt;p&gt;The outputs from this module allow us to authenticate, push/pull container images to/from the registry instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with the Artifact Registry instance repository
&lt;/h2&gt;

&lt;p&gt;If we have an instance available, we want to be able to interact with it, right?&lt;/p&gt;

&lt;p&gt;Let's quickly run through the sequence of commands to pull a public image, tag it, and then push it to the registry instance repository.&lt;/p&gt;

&lt;p&gt;E.g.,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! Authenticate&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cat &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.ssh/terraform-sa.json"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-w0&lt;/span&gt; | docker login &lt;span class="nt"&gt;-u&lt;/span&gt; _json_key_base64 &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; us-west2-docker.pkg.dev
&lt;span class="c"&gt;#! Pull a public image from Dockerhub&lt;/span&gt;
docker pull busybox
&lt;span class="c"&gt;#! Tag the image&lt;/span&gt;
docker tag busybox us-west2-docker.pkg.dev/fe-cphillipson/tanzu/busybox
&lt;span class="c"&gt;#! Push the image to the Artifact Registry instance repository&lt;/span&gt;
docker push us-west2-docker.pkg.dev/fe-cphillipson/tanzu/busybox

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Provisioning a cloud environment
&lt;/h2&gt;

&lt;p&gt;To be able to install &lt;a href="https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/1.4/tap/online-install-intro.html" rel="noopener noreferrer"&gt;Tanzu Application Platform&lt;/a&gt;, we'll need multiple resources provisioned. I've curated a set of Terraform modules to do that, &lt;a href="https://github.com/pacphi/gha-workflows-with-gitops-for-tanzu-application-platform/tree/main/terraform/google" rel="noopener noreferrer"&gt;here&lt;/a&gt;. At a minimum, consider executing these modules: &lt;em&gt;iam&lt;/em&gt;, &lt;em&gt;project&lt;/em&gt;, &lt;em&gt;kms&lt;/em&gt;, &lt;em&gt;registry&lt;/em&gt;, &lt;em&gt;virtual-network&lt;/em&gt;, &lt;em&gt;cluster&lt;/em&gt;, &lt;em&gt;main-dns&lt;/em&gt;, and &lt;em&gt;child-dns&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Tanzu Application Platform
&lt;/h2&gt;

&lt;p&gt;While I provided a link to install TAP (in online-mode), we're &lt;strong&gt;not&lt;/strong&gt; going to follow those instructions. As I mentioned earlier, I wanted to put myself in a position to repeat these steps with ease and minimal toil and friction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I am setting the stage to share a series of posts with you about how I assembled automation targeting multiple cloud providers&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With that in mind, we're going to stare at some &lt;a href="https://yaml.org/" rel="noopener noreferrer"&gt;YAML&lt;/a&gt;, because who doesn't like to do that, huh?&lt;/p&gt;

&lt;p&gt;But before we do that we need to satisfy some prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://network.pivotal.io/docs/faq#feature_requests" rel="noopener noreferrer"&gt;Register&lt;/a&gt; for a new account on Tanzu Network&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/1.4/tap/install-tanzu-cli.html#accept-the-end-user-license-agreements-0" rel="noopener noreferrer"&gt;Accept the EULAs&lt;/a&gt; for VMWare Tanzu Application Platform.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we need to create a namespace and install two controllers into the cluster. &lt;em&gt;(You will already have administrator access if you used the Terraform cluster module above).&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! Create namespace where TAP will be installed&lt;/span&gt;
kubectl create namespace tap-install

&lt;span class="c"&gt;#! Install kapp-controller&lt;/span&gt;
&lt;span class="c"&gt;#! @see https://carvel.dev/kapp-controller/docs/v0.43.2/install/&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/vmware-tanzu/carvel-kapp-controller/releases/latest/download/release.yml

&lt;span class="c"&gt;#! Install secretgen-controller&lt;/span&gt;
&lt;span class="c"&gt;#! @see https://github.com/carvel-dev/secretgen-controller/blob/develop/docs/install.md&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/carvel-dev/secretgen-controller/releases/latest/download/release.yml

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

&lt;/div&gt;



&lt;p&gt;Now, let's create some directories&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;tap-install
&lt;span class="nb"&gt;cd &lt;/span&gt;tap-install
&lt;span class="nb"&gt;mkdir &lt;/span&gt;base
&lt;span class="nb"&gt;mkdir &lt;/span&gt;config
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; profiles/iterate

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

&lt;/div&gt;



&lt;p&gt;And here beginneth a wave of templated YAML files.&lt;/p&gt;

&lt;p&gt;Place the following files in the tap-install/ &lt;strong&gt;base&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;rbac.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-default-sa&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-install/rbac&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap-install/tap"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-default-role&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-install/rbac&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap-install/tap"&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-default-role-binding&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-install/rbac&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap-install/tap"&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-default-sa&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-default-role&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;container-image-registry-secret.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="c1"&gt;#@ load("@ytt:json", "json")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;#@ def crc_config():&lt;/span&gt;
&lt;span class="c1"&gt;#@ return {&lt;/span&gt;
&lt;span class="c1"&gt;#@ "auths": {&lt;/span&gt;
&lt;span class="c1"&gt;#@ data.values.tap.credentials.registry.host: {&lt;/span&gt;
&lt;span class="c1"&gt;#@ "email": data.values.tap.credentials.registry.email,&lt;/span&gt;
&lt;span class="c1"&gt;#@ "username": data.values.tap.credentials.registry.username,&lt;/span&gt;
&lt;span class="c1"&gt;#@ "password": data.values.tap.credentials.registry.password&lt;/span&gt;
&lt;span class="c1"&gt;#@ }&lt;/span&gt;
&lt;span class="c1"&gt;#@ }&lt;/span&gt;
&lt;span class="c1"&gt;#@ }&lt;/span&gt;
&lt;span class="c1"&gt;#@ end&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container-registry-credentials&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap"&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/dockerconfigjson&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;.dockerconfigjson&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ json.encode(crc_config())&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secretgen.carvel.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretExport&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container-registry-credentials&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;toNamespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;dev-namespace.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;#@ if data.values.tap.devNamespace != "default" and data.values.tap.devNamespace != "":&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.devNamespace&lt;/span&gt;
&lt;span class="c1"&gt;#@ end&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secretgen.carvel.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretImport&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container-registry-credentials&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.devNamespace&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fromNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secretgen.carvel.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretImport&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tanzu-network-credentials&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.devNamespace&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fromNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.devNamespace&lt;/span&gt;
&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container-registry-credentials&lt;/span&gt;
&lt;span class="na"&gt;imagePullSecrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container-registry-credentials&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tanzu-network-credentials&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ "{}-{}".format(data.values.tap.devNamespace, "permit-deliverable")&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.devNamespace&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deliverable&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ "{}-{}".format(data.values.tap.devNamespace, "permit-workload")&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.devNamespace&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workload&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;tanzu-network-secret.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="c1"&gt;#@ load("@ytt:base64", "base64")&lt;/span&gt;
&lt;span class="c1"&gt;#@ load("@ytt:json", "json")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;#@ def tn_config():&lt;/span&gt;
&lt;span class="c1"&gt;#@ return {&lt;/span&gt;
&lt;span class="c1"&gt;#@ "auths": {&lt;/span&gt;
&lt;span class="c1"&gt;#@ data.values.tap.credentials.tanzuNet.host: {&lt;/span&gt;
&lt;span class="c1"&gt;#@ "username": data.values.tap.credentials.tanzuNet.username,&lt;/span&gt;
&lt;span class="c1"&gt;#@ "password": data.values.tap.credentials.tanzuNet.password&lt;/span&gt;
&lt;span class="c1"&gt;#@ }&lt;/span&gt;
&lt;span class="c1"&gt;#@ }&lt;/span&gt;
&lt;span class="c1"&gt;#@ }&lt;/span&gt;
&lt;span class="c1"&gt;#@ end&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tanzu-network-credentials&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deleting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap"&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/dockerconfigjson&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;.dockerconfigjson&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ json.encode(tn_config())&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secretgen.carvel.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretExport&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tanzu-network-credentials&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;toNamespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;tap-package-repo.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#@ load("@ytt:data", "data")
---
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageRepository
metadata:
  name: tanzu-tap-repository
  namespace: #@ data.values.tap.namespace
  annotations:
    kapp.k14s.io/change-group: tap-install/tap-repo
spec:
  fetch:
    imgpkgBundle:
      image: #@ "{}/tanzu-application-platform/tap-packages:{}".format(data.values.tap.credentials.tanzuNet.host, data.values.tap.version)
      secretRef:
        name: tanzu-network-credentials

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;tap-package-install.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packaging.carvel.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PackageInstall&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-install/tap&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule.0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upsert&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;upserting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap-install/rbac"&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule.1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upsert&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;upserting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap-install/tap-repo"&lt;/span&gt;
    &lt;span class="na"&gt;packaging.carvel.dev/downgradable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;packageRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;refName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap.tanzu.vmware.com&lt;/span&gt;
    &lt;span class="na"&gt;versionSelection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ str(data.values.tap.version)&lt;/span&gt;
      &lt;span class="na"&gt;prereleases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-default-sa&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-values&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Place the following files in the tap-install/profiles/ &lt;strong&gt;iterate&lt;/strong&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tbs-full-dependencies-package-repo.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packaging.carvel.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PackageRepository&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tbs-full-deps-repository&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-install/tbs-full-deps-repo&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;imgpkgBundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ "{}/tanzu-application-platform/full-tbs-deps-package-repo:{}".format(data.values.tap.credentials.tanzuNet.host, data.values.buildService.version)&lt;/span&gt;
      &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tanzu-network-credentials&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;tbs-full-dependencies-package-install.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="c1"&gt;#@ load("@ytt:yaml", "yaml")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;#@ def stack_config():&lt;/span&gt;
  &lt;span class="na"&gt;stack_configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.stack_configuration&lt;/span&gt;
&lt;span class="c1"&gt;#@ end&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packaging.carvel.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PackageInstall&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tbs-full-dependencies&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-install/tbs-full-deps&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule.0&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upsert&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;upserting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap-install/tap"&lt;/span&gt;
    &lt;span class="na"&gt;kapp.k14s.io/change-rule.1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upsert&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;after&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;upserting&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tap-install/tbs-full-deps-repo"&lt;/span&gt;
    &lt;span class="na"&gt;packaging.carvel.dev/downgradable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;packageRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;refName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full-tbs-deps.tanzu.vmware.com&lt;/span&gt;
    &lt;span class="na"&gt;versionSelection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;constraints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ str(data.values.buildService.version)&lt;/span&gt;
      &lt;span class="na"&gt;prereleases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-default-sa&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tbs-full-dependencies&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tbs-full-dependencies&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tbs-full-dependencies-secrets.yml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ yaml.encode(stack_config())&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Place the following files in the tap-install/ &lt;strong&gt;config&lt;/strong&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tap-iterate-values-template.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@ load("@ytt:data", "data")&lt;/span&gt;
&lt;span class="c1"&gt;#@ load("@ytt:yaml", "yaml")&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;#@ def config():&lt;/span&gt;
&lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iterate&lt;/span&gt;

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

&lt;span class="na"&gt;shared&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingress_domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.domains.main&lt;/span&gt;

&lt;span class="na"&gt;buildservice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kp_default_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ "{}/{}".format(data.values.tap.credentials.registry.host, data.values.tap.registry.repositories.buildService)&lt;/span&gt;
  &lt;span class="na"&gt;kp_default_repository_username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.credentials.registry.username&lt;/span&gt;
  &lt;span class="na"&gt;kp_default_repository_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.credentials.registry.password&lt;/span&gt;
  &lt;span class="na"&gt;tanzunet_username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.credentials.tanzuNet.username&lt;/span&gt;
  &lt;span class="na"&gt;tanzunet_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.credentials.tanzuNet.password&lt;/span&gt;
  &lt;span class="na"&gt;exclude_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;stack_configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.stack_configuration&lt;/span&gt;

&lt;span class="c1"&gt;#! supply_chain is pinned to basic as we want fast feedback in inner loop development&lt;/span&gt;
&lt;span class="na"&gt;supply_chain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;basic&lt;/span&gt;

&lt;span class="na"&gt;ootb_supply_chain_basic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cluster_builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;
  &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.credentials.registry.host&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.registry.repositories.ootbSupplyChain&lt;/span&gt;
  &lt;span class="na"&gt;gitops&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ssh_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.supply_chain.gitops.ssh_secret&lt;/span&gt;

&lt;span class="na"&gt;scanning&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;metadataStore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

&lt;span class="na"&gt;metadata_store&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ns_for_export_app_cert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.devNamespace&lt;/span&gt;
  &lt;span class="na"&gt;app_service_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;

&lt;span class="na"&gt;image_policy_webhook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow_unmatched_tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;contour&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;envoy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;

&lt;span class="na"&gt;cnrs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;domain_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.domains.knative&lt;/span&gt;
  &lt;span class="na"&gt;domain_template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ "{}-{}-{}".format("{{.Name}}", data.values.tap.domains.suffix, "{{.Namespace}}.{{.Domain}}")&lt;/span&gt;

&lt;span class="na"&gt;appliveview_connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sslDisabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;ingressEnabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ "appliveview.{}".format(data.values.tap.domains.main)&lt;/span&gt;

&lt;span class="c1"&gt;#@ end&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-values&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ data.values.tap.namespace&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;values.yml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#@ yaml.encode(config())&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;config.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@data/values&lt;/span&gt;

&lt;span class="c1"&gt;#@overlay/match-child-defaults missing_ok=True&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;buildService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.9.0"&lt;/span&gt;

&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.4.0"&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-install&lt;/span&gt;
  &lt;span class="na"&gt;devNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
  &lt;span class="na"&gt;catalogs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;

  &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;buildService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_YOUR_GOOGLE_PROJECT_ID/tanzu/build-service&lt;/span&gt;
      &lt;span class="na"&gt;ootbSupplyChain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_YOUR_GOOGLE_PROJECT_ID/tanzu/supply-chain&lt;/span&gt;

  &lt;span class="na"&gt;domains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap.REPLACE_WITH_YOUR_DOMAIN&lt;/span&gt;
    &lt;span class="na"&gt;knative&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps.tap.REPLACE_WITH_YOUR_DOMAIN&lt;/span&gt;
    &lt;span class="na"&gt;tapGui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tap-gui.tap.REPLACE_WITH_YOUR_DOMAIN&lt;/span&gt;
    &lt;span class="na"&gt;suffix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iterate&lt;/span&gt;

  &lt;span class="c1"&gt;#! Change to "jammy-only" if you want to install Tanzu Application Platform with Ubuntu 22.04 (Jammy) as the only available stack&lt;/span&gt;
  &lt;span class="na"&gt;stack_configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default"&lt;/span&gt;

  &lt;span class="na"&gt;supply_chain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cluster_builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;full&lt;/span&gt;
    &lt;span class="c1"&gt;#! choices below are: [go-git, libgit2]&lt;/span&gt;
    &lt;span class="na"&gt;git_implementation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go-git&lt;/span&gt;
    &lt;span class="na"&gt;gitops&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ssh_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;sensitive-config.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#@data/values&lt;/span&gt;

&lt;span class="c1"&gt;#@overlay/match-child-defaults missing_ok=True&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_GOOGLE_REGION-docker.pkg.dev&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;_json_key_base64&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_BASE64_ENCODED_GOOGLE_SERVICE_ACCOUNT_JSON_FILE_CONTENTS&lt;/span&gt;
    &lt;span class="na"&gt;tanzuNet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.tanzu.vmware.com&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_TANZUNET_USERNAME&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_TANZUNET_PASSWORD&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Phew! That was quite a wave. Now we'll marry the configuration with the templated manifest to generate a concrete manifest named &lt;strong&gt;tap-iterate-values.yml&lt;/strong&gt; using &lt;strong&gt;ytt&lt;/strong&gt;. (Make sure you are still in the tap-install directory).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ytt &lt;span class="nt"&gt;-f&lt;/span&gt; config &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tap-iterate-values.yml

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

&lt;/div&gt;



&lt;p&gt;Go ahead and inspect the result, it's just a &lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" rel="noopener noreferrer"&gt;Secret&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;tap-iterate-values.yml

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

&lt;/div&gt;



&lt;p&gt;Let's deploy it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; tap-iterate-values.yml

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

&lt;/div&gt;



&lt;p&gt;To cap it all off we'll use the combination of &lt;strong&gt;ytt&lt;/strong&gt; and &lt;strong&gt;kapp&lt;/strong&gt; to install Tanzu Application Platform activating the &lt;strong&gt;iterate&lt;/strong&gt; &lt;a href="https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/1.4/tap/about-package-profiles.html#installation-profiles-in-tanzu-application-platform-v14-1" rel="noopener noreferrer"&gt;profile&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kapp deploy &lt;span class="nt"&gt;-a&lt;/span&gt; tap-iterate &lt;span class="nt"&gt;-f&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;ytt &lt;span class="nt"&gt;-f&lt;/span&gt; config/config.yml &lt;span class="nt"&gt;-f&lt;/span&gt; config/sensitive-config.yml &lt;span class="nt"&gt;-f&lt;/span&gt; base &lt;span class="nt"&gt;-f&lt;/span&gt; profiles/iterate&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Execute the following command to see that all components were installed successfully. You may have to do this multiple times if you're impatient. A typical installation completes within 20 minutes. &lt;em&gt;(You'll know that when the DESCRIPTION column shows Reconcile succeeded for all installed components)&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get app &lt;span class="nt"&gt;-A&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;We spent a fair amount of time cultivating HCL and YAML to drive the desired outcome of automated provisioning of public cloud resources and installation of TAP. And we were exposed to some sharp tools. However, exercising the building blocks above still leads to toil.&lt;/p&gt;

&lt;p&gt;What if we were asked to create multiple environments? No doubt, we'd start crying.&lt;/p&gt;

&lt;p&gt;And what about collaboration? I would want the &lt;a href="https://en.wikipedia.org/wiki/System_of_record" rel="noopener noreferrer"&gt;system of record&lt;/a&gt; to be a &lt;a href="https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository" rel="noopener noreferrer"&gt;Git repository&lt;/a&gt;, not my laptop. I don't want to tie up my laptop compute either (when provisioning cloud resources with Terraform), so I would consider leveraging a self-service continuous integration/delivery engine.&lt;/p&gt;

&lt;p&gt;What else might we do to improve upon this approach?&lt;/p&gt;

&lt;p&gt;Stay tuned for my next post.&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>Installing cert-manager webhook for OCI and Let's Encrypt ClusterIssuer on an OKE cluster</title>
      <dc:creator>Chris Phillipson</dc:creator>
      <pubDate>Tue, 24 Jan 2023 02:00:45 +0000</pubDate>
      <link>https://dev.to/pacphi/installing-cert-manager-webhook-for-oci-and-lets-encrypt-clusterissuer-on-an-oke-cluster-2892</link>
      <guid>https://dev.to/pacphi/installing-cert-manager-webhook-for-oci-and-lets-encrypt-clusterissuer-on-an-oke-cluster-2892</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxc3crq4nfz31zpz4ad7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxc3crq4nfz31zpz4ad7i.png" width="496" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're an operator maintaining a footprint of &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt; clusters, then you already know that there's more to provisioning and granting access to a cluster. For a cluster to be useful for an end consumer an operator will usually install, enable and configure several other components. One such component is &lt;a href="https://cert-manager.io/" rel="noopener noreferrer"&gt;cert-manager&lt;/a&gt;. Its purpose is to issue certificates from a variety of supported sources. Consulting the &lt;a href="https://cert-manager.io/docs/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, you might learn about the &lt;a href="https://cert-manager.io/docs/configuration/acme/" rel="noopener noreferrer"&gt;ACME issuer type&lt;/a&gt; and the &lt;a href="https://cert-manager.io/docs/configuration/acme/dns01/" rel="noopener noreferrer"&gt;DNS01 challenge validation&lt;/a&gt; method. Then depending on your &lt;a href="https://www.gartner.com/en/information-technology/glossary/infrastructure-as-a-service-iaas" rel="noopener noreferrer"&gt;IaaS&lt;/a&gt; requirements, you will find a limited set of &lt;a href="https://cert-manager.io/docs/configuration/acme/dns01/#supported-dns01-providers" rel="noopener noreferrer"&gt;supported providers&lt;/a&gt; and &lt;a href="https://cert-manager.io/docs/configuration/acme/dns01/#webhook" rel="noopener noreferrer"&gt;webhooks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And now we come to the reason why I'm writing this post. If you're contemplating installing cert-manager an &lt;a href="https://www.oracle.com/cloud/cloud-native/container-engine-kubernetes/" rel="noopener noreferrer"&gt;OKE&lt;/a&gt; cluster on &lt;a href="https://www.oracle.com/cloud/" rel="noopener noreferrer"&gt;OCI&lt;/a&gt;, you'll want to employ a webhook. However, it is not as straightforward as you might think. Uninitiated, you might visit &lt;a href="https://gitlab.com/dn13/cert-manager-webhook-oci" rel="noopener noreferrer"&gt;this link&lt;/a&gt; and follow the instructions to install it. The implementation has not kept up with the API changes over successive releases of cert-manager. So, you would be forced to install a pre-v1.x version of cert-manager. Not a great option as you'd be left open to many security vulnerabilities. I want to share how I built, tested and published an up-to-date version of the aftermentioned, &lt;a href="https://github.com/pacphi/cert-manager-webhook-oci" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading the implementation
&lt;/h2&gt;

&lt;p&gt;I decided to target &lt;a href="https://cert-manager.io/docs/installation/supported-releases/" rel="noopener noreferrer"&gt;compatibility&lt;/a&gt; with cert-manager v1.10 or better, so that the updated webhook would be functional on Kubernetes clusters, versions 1.20 through 1.26.&lt;/p&gt;

&lt;p&gt;As I reviewed the Gitlab repository where the original webhook implementation and Helm chart reside, I noticed that another developer had made an effort to upgrade the webhook to be compatible with cert-manager 1.8 in this &lt;a href="https://gitlab.com/jcotton/cert-manager-webhook-oci/-/tree/fix_and_update" rel="noopener noreferrer"&gt;fork&lt;/a&gt;. So, I cloned that fork and began to make updates.&lt;/p&gt;

&lt;p&gt;I upgraded the &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Golang&lt;/a&gt; version in &lt;code&gt;go.mod&lt;/code&gt; to 1.19 and decided to target Kubernetes version 1.24 in &lt;code&gt;Makefile&lt;/code&gt;. I also updated the args in &lt;code&gt;Dockerfile&lt;/code&gt; to be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ARG&lt;/span&gt; &lt;span class="n"&gt;GOLANG_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.19&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;ARG&lt;/span&gt; &lt;span class="n"&gt;ALPINE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.17&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;First, I upgraded the libraries&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; all
go mod tidy

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

&lt;/div&gt;



&lt;p&gt;Then, I attempted to build the container image that would be employed by the Helm chart.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; pacphi/cert-manager-webhook-oci &lt;span class="nb"&gt;.&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Where to host this image? In a public container image registry repository. I chose an &lt;a href="https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/registry/index.html" rel="noopener noreferrer"&gt;OCI registry&lt;/a&gt; instead of &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Dockerhub&lt;/a&gt; because I (and others who might want to consume my pre-built image) won't get &lt;a href="https://docs.docker.com/docker-hub/download-rate-limit/" rel="noopener noreferrer"&gt;rate-limited&lt;/a&gt; on image pull requests.&lt;/p&gt;

&lt;p&gt;Lastly, I authored a &lt;a href="https://www.gnu.org/software/bash/" rel="noopener noreferrer"&gt;BaSH&lt;/a&gt; &lt;a href="https://github.com/pacphi/cert-manager-webhook-oci/blob/main/scripts/prepare-cert-manager-webhook-oci.sh" rel="noopener noreferrer"&gt;script&lt;/a&gt; to help me remember the sequence of steps to create a repo (if it doesn't already exist), build and tag the image, obtain an auth token, authenticate to the registry, and finally push the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspecting the image
&lt;/h2&gt;

&lt;p&gt;I put myself in the shoes of a potential consumer. Could they successfully pull the image with no authentication credentials from the public repository I had just created? And how might they learn about what's truly inside the image?&lt;/p&gt;

&lt;p&gt;To pull the image, they'd execute&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull phx.ocir.io/axyd58snjxbf/cert-manager-webhook-oci:latest

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

&lt;/div&gt;



&lt;p&gt;To explore the layers of the image, they could use &lt;a href="https://github.com/wagoodman/dive" rel="noopener noreferrer"&gt;dive&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dive phx.ocir.io/axyd58snjxbf/cert-manager-webhook-oci:latest

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Upgrading the Helm chart
&lt;/h2&gt;

&lt;p&gt;The chart configuration required only one update. In &lt;a href="https://github.com/pacphi/cert-manager-webhook-oci/blob/main/deploy/cert-manager-webhook-oci/templates/apiservice.yaml" rel="noopener noreferrer"&gt;apiservice.yaml&lt;/a&gt;, I needed to revert all occurrences of &lt;code&gt;v1&lt;/code&gt; back to &lt;code&gt;v1alpha&lt;/code&gt;. Why? The &lt;code&gt;acme.d-n.be&lt;/code&gt; apiservice is not discoverable otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing and uninstalling the Helm chart
&lt;/h2&gt;

&lt;p&gt;Instructions in the &lt;a href="https://gitlab.com/dn13/cert-manager-webhook-oci/-/blob/develop/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt; of the original repository left much to be desired. So, I authored another BaSH &lt;a href="https://github.com/pacphi/cert-manager-webhook-oci/blob/main/scripts/install-cert-manager-webhook-oci.sh" rel="noopener noreferrer"&gt;script&lt;/a&gt; to help me consistently install the prerequisites and the webhook itself. And what about teardown? There's a &lt;a href="https://github.com/pacphi/cert-manager-webhook-oci/blob/main/scripts/uninstall-cert-manager-webhook-oci.sh" rel="noopener noreferrer"&gt;script&lt;/a&gt; for that too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it out
&lt;/h2&gt;

&lt;p&gt;Before you can leverage the install and uninstall scripts, you'll need to:&lt;/p&gt;

&lt;p&gt;* &lt;a href="https://docs.oracle.com/en-us/iaas/Content/DNS/Tasks/managingdnszones.htm" rel="noopener noreferrer"&gt;add a DNS zone&lt;/a&gt; in OCI&lt;br&gt;&lt;br&gt;
* &lt;a href="https://docs.oracle.com/en/learn/lab_virtual_network/index.html#introduction" rel="noopener noreferrer"&gt;add a VCN&lt;/a&gt; in OCI&lt;br&gt;&lt;br&gt;
* provision a cluster&lt;br&gt;&lt;br&gt;
* update the install script's environment variables&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That last bullet I might improve upon soon so that you don't need to edit the script itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's a fast and inexpensive way to test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! Create a single-node cluster on your workstation with kind; @see https://kind.sigs.k8s.io/&lt;/span&gt;
kind create cluster
&lt;span class="c"&gt;#! Setup access to your Oracle Cloud Infastructure account&lt;/span&gt;
oci setup config
&lt;span class="c"&gt;#! Clone the repo where the updated webhook implementation resides&lt;/span&gt;
git clone https://github.com/paphi/cert-manager-webhook-oci
&lt;span class="c"&gt;#! Change directories into the scripts directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;cert-manager-webhook-oci/scripts
&lt;span class="c"&gt;#! Edit and save updates to install-cert-manager-webhook-oci.sh&lt;/span&gt;
&lt;span class="c"&gt;#! Then run this script to install&lt;/span&gt;
./install-cert-manager-webhook-oci.sh
&lt;span class="c"&gt;#! To validate the installation...&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl get apiservice | &lt;span class="nb"&gt;grep &lt;/span&gt;acme.d-n.be
kubectl get clusterissuer
kubectl get cert &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl get secret &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;span class="c"&gt;#! To uninstall, run this script&lt;/span&gt;
./uninstall-cert-manager-webhook-oci.sh
&lt;span class="c"&gt;#! To destroy the cluster&lt;/span&gt;
kind delete cluster

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

&lt;/div&gt;



&lt;p&gt;But if you want to test this out against an OKE cluster, you could follow &lt;a href="https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/oke-full/index.html" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>welcome</category>
    </item>
  </channel>
</rss>
