<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: DarkEdges</title>
    <description>The latest articles on Forem by DarkEdges (@darkedges).</description>
    <link>https://forem.com/darkedges</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%2F1396307%2F1b5a1a60-77b1-40cd-89e3-0cfb704caf5a.jpeg</url>
      <title>Forem: DarkEdges</title>
      <link>https://forem.com/darkedges</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/darkedges"/>
    <language>en</language>
    <item>
      <title>Enriching Vault OIDC Tokens with SPIFFE Identity Metadata using Terraform</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Wed, 03 Dec 2025 19:39:08 +0000</pubDate>
      <link>https://forem.com/darkedges/enriching-vault-oidc-tokens-with-spiffe-identity-metadata-using-terraform-314g</link>
      <guid>https://forem.com/darkedges/enriching-vault-oidc-tokens-with-spiffe-identity-metadata-using-terraform-314g</guid>
      <description>&lt;p&gt;In modern microservices architectures, machine identity is just as critical as human identity. When services communicate, they often need to prove not just &lt;em&gt;who&lt;/em&gt; they are, but &lt;em&gt;what&lt;/em&gt; they are—their environment, business unit, or SPIFFE ID.&lt;/p&gt;

&lt;p&gt;HashiCorp Vault is excellent for this. Its Identity Secrets Engine can issue OIDC tokens that serve as verifiable credentials for your workloads. However, getting those tokens to contain rich, custom metadata requires connecting a few specific dots: &lt;strong&gt;AppRole&lt;/strong&gt;, &lt;strong&gt;Identity Entities&lt;/strong&gt;, and &lt;strong&gt;OIDC Templates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through how to implement a robust machine identity pipeline using Terraform, where an application authenticates via AppRole and receives an OIDC token enriched with custom metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;We want an application (e.g., a "ChatBot") to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate to Vault using the &lt;strong&gt;AppRole&lt;/strong&gt; method.&lt;/li&gt;
&lt;li&gt;Request an &lt;strong&gt;OIDC token&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Receive a token containing custom claims like &lt;code&gt;spiffe_id&lt;/code&gt;, &lt;code&gt;business_unit&lt;/code&gt;, and &lt;code&gt;environment&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Define the Identity Entity
&lt;/h2&gt;

&lt;p&gt;First, we define the "who". In Vault, an &lt;strong&gt;Entity&lt;/strong&gt; represents a unique identity. This is where we store the metadata we want to eventually see in our token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# identities.tf&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"vault_identity_entity"&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_identities_map&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;

  &lt;span class="c1"&gt;# This metadata is what we'll inject into the token later&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
    &lt;span class="nx"&gt;business_unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;business_unit&lt;/span&gt;
    &lt;span class="nx"&gt;spiffe_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"spiffe://vault/application/${each.value.identity.environment}/${each.value.identity.business_unit}/${each.value.identity.name}"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Configure AppRole Authentication
&lt;/h2&gt;

&lt;p&gt;Next, we configure the &lt;strong&gt;AppRole&lt;/strong&gt; auth backend. This is the standard way for machines to authenticate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# approle.tf&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"vault_approle_auth_backend_role"&lt;/span&gt; &lt;span class="s2"&gt;"applications"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_identities_map&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vault_auth_backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
  &lt;span class="nx"&gt;role_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
  &lt;span class="nx"&gt;token_ttl&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
  &lt;span class="nx"&gt;bind_secret_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The "Secret Sauce": Binding AppRole to the Entity
&lt;/h3&gt;

&lt;p&gt;This is the most critical step. By default, when you log in with AppRole, Vault creates a generic entity for that role. To use our custom metadata defined in Step 1, we must explicitly bind the AppRole to our specific Entity using an &lt;strong&gt;Entity Alias&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important Gotcha:&lt;/strong&gt; When creating an alias for AppRole, the &lt;code&gt;name&lt;/code&gt; of the alias must be the &lt;strong&gt;Role ID&lt;/strong&gt;, not the Role Name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# approle.tf&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"vault_identity_entity_alias"&lt;/span&gt; &lt;span class="s2"&gt;"approle_applications"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_identities_map&lt;/span&gt;

  &lt;span class="c1"&gt;# CRITICAL: Use role_id, not role_name&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vault_approle_auth_backend_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;role_id&lt;/span&gt;

  &lt;span class="nx"&gt;mount_accessor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vault_auth_backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;approle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessor&lt;/span&gt;
  &lt;span class="nx"&gt;canonical_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vault_identity_entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to ensure the AppRole inherits the entity's properties. We can enforce this via a generic endpoint configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"vault_generic_endpoint"&lt;/span&gt; &lt;span class="s2"&gt;"approle_entity_inherit"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_identities_map&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;vault_approle_auth_backend_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"auth/approle/role/${each.key}"&lt;/span&gt;

  &lt;span class="nx"&gt;data_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;entity_alias_sole_inherit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Configure the OIDC Template
&lt;/h2&gt;

&lt;p&gt;Finally, we configure the OIDC provider and role. The &lt;code&gt;template&lt;/code&gt; field is where the magic happens. We use Vault's template syntax to pull values dynamically from the authenticated entity's metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# identity_tokens.tf&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"vault_identity_oidc_role"&lt;/span&gt; &lt;span class="s2"&gt;"application_identity"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application_identity"&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vault_identity_oidc_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;client_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"spiffe://vault.darkedges.au/gateway"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;

  &lt;span class="c1"&gt;# The Template: Injecting metadata into the JSON payload&lt;/span&gt;
  &lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
{
  "azp": {{identity.entity.metadata.spiffe_id}},
  "nbf": {{time.now}},
  "groups": {{identity.entity.groups.names}},
  "appinfo": {
    "business_unit": {{identity.entity.metadata.business_unit}},
    "environment": {{identity.entity.metadata.environment}}
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOT
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Testing the Flow
&lt;/h2&gt;

&lt;p&gt;Now, let's see it in action. We'll use PowerShell to simulate the application workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Get Credentials &amp;amp; Login
&lt;/h3&gt;

&lt;p&gt;First, we retrieve the Role ID and Secret ID, then authenticate to get a Vault token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get Role ID and Secret ID&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ROLE_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;docker-compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;role_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth/approle/role/ChatBot/role-id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$SECRET_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;docker-compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;secret_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth/approle/role/ChatBot/secret-id&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Login&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$APPTOKEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;docker-compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth/approle/login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;role_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROLE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;secret_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SECRET_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Request the OIDC Token
&lt;/h3&gt;

&lt;p&gt;Using the Vault token we just got, we request the OIDC token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$OIDC_TOKEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;docker-compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APPTOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;identity/oidc/token/application_identity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The Result
&lt;/h3&gt;

&lt;p&gt;When we decode the JWT, we see our rich metadata is successfully populated!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appinfo"&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="nl"&gt;"business_unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"engineering"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production"&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="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://vault.darkedges.au/gateway"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"azp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://vault/application/production/engineering/ChatBot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1764848993&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"groups"&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="s2"&gt;"ChatBot group"&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="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1764762593&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://vault.darkedges.au/v1/identity/oidc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ea0e0006-d4f5-cbde-3cb6-c013d4dba5f2"&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining &lt;strong&gt;Identity Entities&lt;/strong&gt;, &lt;strong&gt;AppRole Aliases&lt;/strong&gt;, and &lt;strong&gt;OIDC Templates&lt;/strong&gt;, we've turned Vault into a powerful identity provider for our internal services. This setup allows downstream systems (like API gateways or service meshes) to make authorization decisions based on trusted, verifiable metadata like &lt;code&gt;business_unit&lt;/code&gt; or &lt;code&gt;environment&lt;/code&gt;, rather than just a raw IP address or generic token.&lt;/p&gt;

&lt;p&gt;a complete example is available at &lt;a href="https://github.com/darkedges/spiffe-vault-terraform" rel="noopener noreferrer"&gt;https://github.com/darkedges/spiffe-vault-terraform&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ausmartway/vault-config-as-code" rel="noopener noreferrer"&gt;https://github.com/ausmartway/vault-config-as-code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/macquarie-engineering-blog/embracing-modern-identity-with-spiffe-and-hashicorp-vault-a-macquarie-bank-journey-9f17ae748f82" rel="noopener noreferrer"&gt;https://medium.com/macquarie-engineering-blog/embracing-modern-identity-with-spiffe-and-hashicorp-vault-a-macquarie-bank-journey-9f17ae748f82&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Auto-Detecting CSV Schemas for Lightning-Fast ClickHouse Ingestion with Parquet</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Fri, 07 Nov 2025 09:06:41 +0000</pubDate>
      <link>https://forem.com/darkedges/auto-detecting-csv-schemas-for-lightning-fast-clickhouse-ingestion-with-parquet-34d9</link>
      <guid>https://forem.com/darkedges/auto-detecting-csv-schemas-for-lightning-fast-clickhouse-ingestion-with-parquet-34d9</guid>
      <description>&lt;p&gt;As a data engineer, one of the most repetitive tasks I face is ingesting data from CSV files. The problem isn't just loading the data; it's the ceremony that comes with it. Every time a new data source appears, I have to manually inspect the columns, define a table schema, and write a script to load it. What if the CSV has 100 columns? What if the data types are ambiguous? This process is tedious and error-prone.&lt;/p&gt;

&lt;p&gt;I wanted a better way. My goal was to create a Node.js script that could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Read any CSV file&lt;/strong&gt; without prior knowledge of its structure.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Auto-detect the schema&lt;/strong&gt;, including column names and data types.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Convert the CSV to Parquet&lt;/strong&gt;, a highly efficient columnar storage format.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Prepare for ingestion&lt;/strong&gt; into ClickHouse, which loves Parquet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, I'll walk you through the proof-of-concept I built to solve this exact problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Parquet and ClickHouse?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ClickHouse&lt;/strong&gt; is an open-source, column-oriented database built for speed. It's a beast for analytical queries (OLAP).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache Parquet&lt;/strong&gt; is a columnar storage format. Instead of storing data in rows, it stores it in columns. This is a perfect match for ClickHouse because it allows the database to read only the columns it needs for a query, dramatically reducing I/O and speeding up performance.&lt;/p&gt;

&lt;p&gt;Combining the two means you get efficient storage and lightning-fast analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concept: From CSV to Parquet
&lt;/h2&gt;

&lt;p&gt;Our script will perform a three-step process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Schema Detection&lt;/strong&gt;: Read the CSV headers and a few sample rows to infer the data type of each column (e.g., &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Data Transformation&lt;/strong&gt;: Convert the raw string values from the CSV into their inferred types.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Parquet Conversion&lt;/strong&gt;: Write the transformed data and the detected schema into a new &lt;code&gt;.parquet&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start with a simple CSV file to demonstrate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Example CSV: &lt;code&gt;sample-data.csv&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id,first_name,last_name,email,is_active,created_at,balance
1,John,Doe,john.doe@example.com,true,2023-01-15T10:00:00Z,150.75
2,Jane,Smith,jane.smith@example.com,false,2023-02-20T11:30:00Z,200.00
3,Peter,Jones,peter.jones@example.com,true,2023-03-10T09:05:00Z,50.25
4,Mary,Williams,mary.w@example.com,true,2023-04-01T15:45:00Z,300.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file has a nice mix of data types: integers, strings, booleans, timestamps, and floating-point numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;First, let's set up a simple Node.js project and install the necessary libraries. We'll use &lt;code&gt;papaparse&lt;/code&gt; for robust CSV parsing and &lt;code&gt;parquetjs&lt;/code&gt; for writing Parquet files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;papaparse parquetjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Auto-Detecting the Schema
&lt;/h2&gt;

&lt;p&gt;This is the core of our solution. We need a function that can look at the string data from the CSV and make an educated guess about its real type.&lt;/p&gt;

&lt;p&gt;Here’s a simple type inference function. It checks if a value is a boolean, a number, or a date. If it's none of those, it defaults to a string.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Function to infer data type from a string value&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;inferType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BOOLEAN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOUBLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Use DOUBLE for all numbers for simplicity&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UTF8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Dates will be stored as strings (UTF8)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UTF8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Default to string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can use this to build a schema from the CSV file. We'll read the first data row to infer the types for each column.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;papa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;papaparse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ... inferType function from above ...&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;papa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error parsing CSV for schema detection.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;firstRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstRecord&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;inferType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we run &lt;code&gt;detectSchema('sample-data.csv')&lt;/code&gt;, it will produce an object like this, which is exactly the format &lt;code&gt;parquetjs&lt;/code&gt; needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DOUBLE"&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="nl"&gt;"first_name"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UTF8"&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="nl"&gt;"last_name"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UTF8"&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="nl"&gt;"email"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UTF8"&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="nl"&gt;"is_active"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BOOLEAN"&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="nl"&gt;"created_at"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UTF8"&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="nl"&gt;"balance"&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="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DOUBLE"&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;h2&gt;
  
  
  Step 3: Converting CSV to Parquet
&lt;/h2&gt;

&lt;p&gt;With the schema detected, we can now read the entire CSV and write it to a Parquet file. The process involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Creating a &lt;code&gt;ParquetSchema&lt;/code&gt; and a &lt;code&gt;ParquetWriter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Iterating through the CSV rows.&lt;/li&gt;
&lt;li&gt; Casting each value to its detected type (e.g., converting the string &lt;code&gt;"true"&lt;/code&gt; to the boolean &lt;code&gt;true&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; Appending the transformed row to the writer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the code to bring it all together:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ParquetSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ParquetWriter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parquetjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ... detectSchema and inferType functions ...&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertCsvToParquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parquetPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Detecting schema...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schemaDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;detectSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ParquetSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schemaDefinition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ParquetWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parquetPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;papa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;skipEmptyLines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; records. Converting to Parquet...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processedRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schemaDefinition&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Cast values to their proper types&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BOOLEAN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;processedRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOUBLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;processedRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;processedRow&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processedRow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Parquet file written to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parquetPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Run the conversion&lt;/span&gt;
&lt;span class="nf"&gt;convertCsvToParquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sample-data.csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;output.parquet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running this script (&lt;code&gt;node index.js&lt;/code&gt;), you'll have an &lt;code&gt;output.parquet&lt;/code&gt; file ready for ClickHouse!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Ingesting into ClickHouse
&lt;/h2&gt;

&lt;p&gt;Now for the easy part. Ingesting the Parquet file into ClickHouse is incredibly simple. First, you need a table with a matching schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_data&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;Float64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="n"&gt;Float64&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeTree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can use the &lt;code&gt;clickhouse-client&lt;/code&gt; to ingest the file directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clickhouse-client &lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"INSERT INTO my_data FORMAT Parquet"&lt;/span&gt; &amp;lt; output.parquet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ClickHouse reads the Parquet file's schema and streams the data directly into the table. It's fast, efficient, and requires no complex &lt;code&gt;INSERT&lt;/code&gt; statements with tons_of_values.&lt;/p&gt;

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

&lt;p&gt;By automating schema detection and converting CSVs to Parquet, we've created a powerful and reusable ETL pipeline. This approach saves a massive amount of time, reduces manual errors, and leverages the high-performance capabilities of both Parquet and ClickHouse.&lt;/p&gt;

&lt;p&gt;This proof-of-concept can be expanded with more robust type detection, error handling, and integration into a larger data workflow, but it's a fantastic starting point for streamlining your data ingestion process.&lt;/p&gt;

</description>
      <category>node</category>
      <category>dataengineering</category>
      <category>database</category>
      <category>automation</category>
    </item>
    <item>
      <title>Uprading Ping Identity Platform to 8.0.0</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Fri, 22 Aug 2025 04:25:24 +0000</pubDate>
      <link>https://forem.com/darkedges/uprading-ping-identity-platform-to-800-5hj2</link>
      <guid>https://forem.com/darkedges/uprading-ping-identity-platform-to-800-5hj2</guid>
      <description>&lt;h2&gt;
  
  
  Ping AM
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue with new config when running amupgrade
&lt;/h3&gt;

&lt;p&gt;The following appears to be added in the wrong place with incorrect details&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmp4tpzznv2vkzhvpujow.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%2Fmp4tpzznv2vkzhvpujow.png" alt=" " width="354" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Error on Startup after migration
&lt;/h3&gt;

&lt;p&gt;When PingAM Starts and you attempt to connect it fails with this in the log.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dfq-am  | {"timestamp":"2025-08-22T04:24:20.258Z","level":"WARN","thread":"http-nio-8080-exec-10","mdc":{"transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-38"},"logger":"org.forgerock.openam.core.realms.impl.DefaultRealmLookup","message":"DefaultRealms:lookup Unable to find Org name for: serverinfo","context":"default","transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-38"}
dfq-am  | {"timestamp":"2025-08-22T04:24:20.397Z","level":"WARN","thread":"http-nio-8080-exec-2","mdc":{"transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-43"},"logger":"org.forgerock.openam.core.realms.impl.DefaultRealmLookup","message":"DefaultRealms:lookup Unable to find Org name for: sessions","context":"default","transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-43"}
dfq-am  | {"timestamp":"2025-08-22T04:24:20.414Z","level":"WARN","thread":"http-nio-8080-exec-2","mdc":{"transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-43"},"logger":"com.sun.identity.monitoring.MonitoringServicesImpl","message":"JDMK runtime not found - Policy Monitoring disabled","context":"default","transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-43"}
dfq-am  | {"timestamp":"2025-08-22T04:24:20.523Z","level":"WARN","thread":"http-nio-8080-exec-2","mdc":{"transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-43"},"logger":"com.sun.identity.idm.plugins.internal.AgentsRepo","message":"AgentsRepo.getAgentGroupConfig: Unable to get Agent Group Config due to The instance agentgroup does not exist","context":"default","transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-43"}
dfq-am  | {"timestamp":"2025-08-22T04:24:20.637Z","level":"WARN","thread":"http-nio-8080-exec-1","mdc":{"transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-56"},"logger":"org.forgerock.openam.core.rest.authn.http.AuthenticationServiceV2","message":"Authentication encountered an error: [Status: 401 Unauthorized]","context":"default","transactionId":"4a84d7a7-8b56-4819-b5c6-6adc4d215e2e-56"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it is missing the following entry in Ping DS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dn: ou=agentgroup,ou=Instances,ou=1.0,ou=AgentService,ou=services,ou=am-config
objectClass: top
objectClass: sunServiceComponent
objectClass: organizationalUnit
ou: agentgroup
sunserviceID: agentgroup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwju9qqnd87h7uu21wuk.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%2Fwwju9qqnd87h7uu21wuk.png" alt=" " width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Creating a Workflow using your Connector</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Sat, 16 Aug 2025 06:34:34 +0000</pubDate>
      <link>https://forem.com/darkedges/creating-a-workflow-using-your-connector-54fk</link>
      <guid>https://forem.com/darkedges/creating-a-workflow-using-your-connector-54fk</guid>
      <description>&lt;p&gt;In order to use connector you need to &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Connection&lt;/li&gt;
&lt;li&gt;Create a Flow&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;Connect to your Okta Workflow instance.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Connections&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;code&gt;Connections&lt;/code&gt; click the &lt;code&gt;New Connection&lt;/code&gt; icon.&lt;/li&gt;
&lt;li&gt;Select Your connector 
&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%2Fgnhwcpcpu44wbsen73e1.png" alt=" " width="545" height="192"&gt;
&lt;/li&gt;
&lt;li&gt;Provide the details and click the &lt;code&gt;Create&lt;/code&gt; button. 
&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%2Fzq9m3ydp0qsik3zvyf2c.png" alt=" " width="562" height="870"&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Connect to your Okta Workflow instance.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Flows&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;code&gt;New Flow&lt;/code&gt; button.&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;When this happens&lt;/code&gt; block click &lt;code&gt;Add event&lt;/code&gt; and select &lt;code&gt;API Endpoint&lt;/code&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%2Fgjf5yvxnlobt0t3kbiig.png" alt=" " width="513" height="571"&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;save&lt;/code&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%2Fmmp4m4iu95doy7gi7kmc.png" alt=" " width="504" height="337"&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Then do this&lt;/code&gt; block click &lt;code&gt;Add function&lt;/code&gt; to add functions.&lt;/li&gt;
&lt;li&gt;When finished it should look like. 
&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%2Fyhuzuisi63dysihbxuq1.png" alt=" " width="800" height="273"&gt;
&lt;/li&gt;
&lt;li&gt;Save it as the name you want to display when selecting the action. Ensure you select ``&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Click the &lt;code&gt;Run&lt;/code&gt; button and click &lt;code&gt;Run&lt;/code&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%2Fnhhw9sohmwam5leb9lrd.png" alt=" " width="370" height="460"&gt;
&lt;/li&gt;
&lt;li&gt;When it has completed the execution it should be successful. 
&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%2Fj8hlcbqdfvgp114jvw4k.png" alt=" " width="800" height="239"&gt;
&lt;/li&gt;
&lt;li&gt;Open the flow directly and it should return similair to 
&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%2Fanp4i309sxo9zfbpo7x5.png" alt=" " width="275" height="20"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;`&lt;br&gt;
{&lt;br&gt;
    "output": {&lt;br&gt;
        "length": 1&lt;br&gt;
    },&lt;br&gt;
    "statusCode": 200&lt;br&gt;
}&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Creating a Connector using the Okta Workflow Connector Builder</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Sat, 16 Aug 2025 06:33:53 +0000</pubDate>
      <link>https://forem.com/darkedges/creating-a-connector-using-the-okta-workflow-connector-builder-1l90</link>
      <guid>https://forem.com/darkedges/creating-a-connector-using-the-okta-workflow-connector-builder-1l90</guid>
      <description>&lt;p&gt;Okta Workflow is a no code development environment to create workflows to perform a large number of operations based on Connections. If there is not a Connector available you can create your own using their Connector Builder, and the best part is that you can try it out using their free Integration account.&lt;/p&gt;

&lt;p&gt;Okta workflow no code approach is drag and drop between functions / actions. Most functions have &lt;code&gt;Inputs&lt;/code&gt; and &lt;code&gt;Outputs&lt;/code&gt;, so you can drag an &lt;code&gt;Output&lt;/code&gt; onto an &lt;code&gt;Input&lt;/code&gt;. Once done you can highlight &lt;code&gt;Input&lt;/code&gt; or &lt;code&gt;Output&lt;/code&gt; to see the connection.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fag8so80vga3287xu1dbs.gif" 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%2Fag8so80vga3287xu1dbs.gif" alt=" " width="1100" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the basic steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Connector.&lt;/li&gt;
&lt;li&gt;Configure Authentication.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;httpHelper&lt;/code&gt; flow - used by the connector to make requests using the connection Acces token.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;_pingAuth&lt;/code&gt; flow - Validates the Access Token to see if it needs to be renewed.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;action&lt;/code&gt; flow- Performs an operation that can be used in Flow.&lt;/li&gt;
&lt;li&gt;Deploy it&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a Connector.
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Connect to your Okta Workflow instance.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Connector Builder&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;code&gt;Connectors&lt;/code&gt; click the &lt;code&gt;+&lt;/code&gt; icon.&lt;/li&gt;
&lt;li&gt;Provide the name for the connector and click the &lt;code&gt;Save&lt;/code&gt; button.&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%2Fvpxywq1ni3uo8dh9zqgo.jpeg" alt=" " width="574" height="480"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configure Authentication.
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Click the &lt;code&gt;Add Authentication&lt;/code&gt; button and fill out the details &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%2Fjswa5ngdihpekcnwwcm8.png" alt=" " width="799" height="484"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a Test Connection
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;code&gt;Test Connections&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;+New Connection&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Provide the details and click the &lt;code&gt;Create&lt;/code&gt; button 
&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%2Fphr96iwgcyevgmf71mvg.png" alt=" " width="604" height="867"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a &lt;code&gt;httpHelper&lt;/code&gt; flo.
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;code&gt;Flows&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;+New Flow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;When this happens&lt;/code&gt; block click &lt;code&gt;Add event&lt;/code&gt; and select &lt;code&gt;Helper Flow&lt;/code&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%2Fx9rydl0p775r4gvzn8eb.png" alt=" " width="800" height="375"&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Then do this&lt;/code&gt; block click &lt;code&gt;Add function&lt;/code&gt; to add functions.&lt;/li&gt;
&lt;li&gt;When finished it should look like. 
&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%2F5oyxzh94m8vv8aunoux7.png" alt=" " width="800" height="252"&gt;
&lt;/li&gt;
&lt;li&gt;Save it as &lt;code&gt;httpHelper&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a &lt;code&gt;_pingAuth&lt;/code&gt; flo.
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;code&gt;Flows&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;+New Flow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;When this happens&lt;/code&gt; block click &lt;code&gt;Add event&lt;/code&gt; and select &lt;code&gt;Authping&lt;/code&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%2Fnq3z96648g1en07tdkpn.png" alt=" " width="785" height="337"&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Then do this&lt;/code&gt; block click &lt;code&gt;Add function&lt;/code&gt; to add functions.&lt;/li&gt;
&lt;li&gt;When finished it should look like. &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%2Fjavnhfhw3299k36sqhsb.png" alt=" " width="800" height="400"&gt;
&lt;/li&gt;
&lt;li&gt;Save it as &lt;code&gt;_authPing&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a &lt;code&gt;action&lt;/code&gt; flo.
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;code&gt;Flows&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;+New Flow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;When this happens&lt;/code&gt; block click &lt;code&gt;Add event&lt;/code&gt; and select &lt;code&gt;Action&lt;/code&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%2Fdpy95o2n96zfjgbgc1d9.png" alt=" " width="776" height="547"&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;Then do this&lt;/code&gt; block click &lt;code&gt;Add function&lt;/code&gt; to add functions.&lt;/li&gt;
&lt;li&gt;When finished it should look like. 
&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%2Fus21tjkz49ezoubqak5u.png" alt=" " width="800" height="561"&gt;
&lt;/li&gt;
&lt;li&gt;Save it as the name you want to display when selecting the action.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;code&gt;Deployment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Validate connector&lt;/code&gt; and then &lt;code&gt;Done&lt;/code&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%2Ffz5qjkkj81yx93zm3d0s.png" alt=" " width="250" height="85"&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Deploy test version&lt;/code&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%2Fd7h92luyjf80ur5jdmcg.png" alt=" " width="350" height="63"&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Deploy local connector&lt;/code&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%2Fm2ig8j3v3tqz2p11h7uq.png" alt=" " width="356" height="61"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gets a connector deployed and ready for using in an actual flow.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Finally testing the solution</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Sat, 16 Aug 2025 02:09:24 +0000</pubDate>
      <link>https://forem.com/darkedges/finally-testing-the-solution-3bkb</link>
      <guid>https://forem.com/darkedges/finally-testing-the-solution-3bkb</guid>
      <description>&lt;p&gt;To recap, we have an Private Application Load Balancer, Fargate ECS Cluster and an AWS API Gateway v2. So know we can test the solution. &lt;/p&gt;

</description>
      <category>aws</category>
      <category>apigateway</category>
      <category>systemdesign</category>
      <category>testing</category>
    </item>
    <item>
      <title>Deploying the AWS API Gateway V2 to LocalStack</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Sat, 16 Aug 2025 02:06:00 +0000</pubDate>
      <link>https://forem.com/darkedges/deploying-the-aws-api-gateway-v2-to-localstack-49ab</link>
      <guid>https://forem.com/darkedges/deploying-the-aws-api-gateway-v2-to-localstack-49ab</guid>
      <description>&lt;p&gt;We are getting close to being able to test our solution, the final piece is the deployment of an AWS API Gateway v2 with an Authorizer that trusts Auth0 tokens.&lt;/p&gt;

&lt;p&gt;For this we will create a number of AWS Configurations&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway V2 will connect to the Fargate API Instance we deployed previously.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a lot of configuration items so they are avaiable in the following branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git switch apiv2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we will plan and then apply the changes if that is green, by using the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform plan
terraform apply --auto-approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it has been deployed you should get the following output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alb = "http://alb.elb.localhost.localstack.cloud:4566/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use this address in a future request to confirm the API has been deployed correctly.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deploying a custom API via AWS Fargate</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Sat, 16 Aug 2025 02:01:06 +0000</pubDate>
      <link>https://forem.com/darkedges/deploying-api-via-aws-fargate-4noi</link>
      <guid>https://forem.com/darkedges/deploying-api-via-aws-fargate-4noi</guid>
      <description>&lt;p&gt;Now we have a Private Application Load Balancer deployed we can look at putting an API behind it. For this we will use an existing container image &lt;code&gt;darkedges/providerapi:1.0.2&lt;/code&gt; which provides a basic API that we are going to serve via an AWS API Gateway V2 protected by an Auth0 Access Token.&lt;/p&gt;

&lt;p&gt;For this we will create a number of AWS Configurations&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECS Cluster as our Fargate service.&lt;/li&gt;
&lt;li&gt;ECS Service Task to deploy the custom API Container&lt;/li&gt;
&lt;li&gt;ALB updates to link it al.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a lot of configuration items so they are avaiable in the following branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git switch fargate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we will plan and then apply the changes if that is green, by using the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform plan
terraform apply --auto-approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it has been deployed you should get the following output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alb = "http://alb.elb.localhost.localstack.cloud:4566/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use this address in a future request to confirm the API has been deployed correctly.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deploying an Private ALB to LocalStack</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Sat, 16 Aug 2025 00:49:39 +0000</pubDate>
      <link>https://forem.com/darkedges/deploying-an-alb-to-localstack-4b01</link>
      <guid>https://forem.com/darkedges/deploying-an-alb-to-localstack-4b01</guid>
      <description>&lt;p&gt;In the previous articles we stood up LocalStack and configured Terraform to plan a deployment. Next we will deploy an ALB to the platform and get its address so that we can use it in the next time.&lt;/p&gt;

&lt;p&gt;For this we will create a number of AWS Configurations&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC to deploy into.&lt;/li&gt;
&lt;li&gt;Networking to allow the ALB to be deployed and connect to the ECS instance.&lt;/li&gt;
&lt;li&gt;SecurityGroups to manage all the network egress / ingress between services.&lt;/li&gt;
&lt;li&gt;ALB the Application Load Balancer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a lot of configuration items so they are avaiable in the following branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git switch alb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we will plan and then apply the changes if that is green, by using the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform plan
terraform apply --auto-approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it has been deployed you should get the following output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alb = "http://alb.elb.localhost.localstack.cloud:4566/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use this address in a future request to confirm the API has been deployed correctly.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Configuring Terraform to deploy into LocalStack</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Fri, 15 Aug 2025 23:44:20 +0000</pubDate>
      <link>https://forem.com/darkedges/configuring-terraform-to-deploy-into-localstack-9jk</link>
      <guid>https://forem.com/darkedges/configuring-terraform-to-deploy-into-localstack-9jk</guid>
      <description>&lt;p&gt;From our previous article we have successfuly registered and deployed LocalStack. Now we are planning to deploy our API onto LocalStack using AWS Fargate via Terraform, but we need to deploy some supporting services first, such as the core Terraform configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform
&lt;/h2&gt;

&lt;p&gt;We are going to be using terraform to manage our state so lets create the &lt;code&gt;terraform&lt;/code&gt; directory and place the following file &lt;code&gt;_provider.tf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region     = "us-east-1"
  access_key = "test"
  secret_key = "test"

  endpoints {
    acm                      = "http://localhost:4566"
    amplify                  = "http://localhost:4566"
    apigateway               = "http://localhost:4566"
    apigatewayv2             = "http://localhost:4566"
    appconfig                = "http://localhost:4566"
    applicationautoscaling   = "http://localhost:4566"
    appsync                  = "http://localhost:4566"
    athena                   = "http://localhost:4566"
    autoscaling              = "http://localhost:4566"
    backup                   = "http://localhost:4566"
    batch                    = "http://localhost:4566"
    cloudformation           = "http://localhost:4566"
    cloudfront               = "http://localhost:4566"
    cloudsearch              = "http://localhost:4566"
    cloudtrail               = "http://localhost:4566"
    cloudwatch               = "http://localhost:4566"
    cloudwatchlogs           = "http://localhost:4566"
    codecommit               = "http://localhost:4566"
    cognitoidentity          = "http://localhost:4566"
    cognitoidp               = "http://localhost:4566"
    config                   = "http://localhost:4566"
    costexplorer             = "http://localhost:4566"
    docdb                    = "http://localhost:4566"
    dynamodb                 = "http://localhost:4566"
    ec2                      = "http://localhost:4566"
    ecr                      = "http://localhost:4566"
    ecs                      = "http://localhost:4566"
    efs                      = "http://localhost:4566"
    eks                      = "http://localhost:4566"
    elasticache              = "http://localhost:4566"
    elasticbeanstalk         = "http://localhost:4566"
    elasticsearch            = "http://localhost:4566"
    elb                      = "http://localhost:4566"
    elbv2                    = "http://localhost:4566"
    emr                      = "http://localhost:4566"
    events                   = "http://localhost:4566"
    firehose                 = "http://localhost:4566"
    glacier                  = "http://localhost:4566"
    glue                     = "http://localhost:4566"
    iam                      = "http://localhost:4566"
    iot                      = "http://localhost:4566"
    iotanalytics             = "http://localhost:4566"
    iotevents                = "http://localhost:4566"
    kafka                    = "http://localhost:4566"
    kinesis                  = "http://localhost:4566"
    kinesisanalytics         = "http://localhost:4566"
    kinesisanalyticsv2       = "http://localhost:4566"
    kms                      = "http://localhost:4566"
    lakeformation            = "http://localhost:4566"
    lambda                   = "http://localhost:4566"
    mediaconvert             = "http://localhost:4566"
    mediastore               = "http://localhost:4566"
    neptune                  = "http://localhost:4566"
    organizations            = "http://localhost:4566"
    qldb                     = "http://localhost:4566"
    rds                      = "http://localhost:4566"
    redshift                 = "http://localhost:4566"
    redshiftdata             = "http://localhost:4566"
    resourcegroups           = "http://localhost:4566"
    resourcegroupstaggingapi = "http://localhost:4566"
    route53                  = "http://localhost:4566"
    route53resolver          = "http://localhost:4566"
    s3                       = "http://s3.localhost.localstack.cloud:4566"
    s3control                = "http://localhost:4566"
    sagemaker                = "http://localhost:4566"
    secretsmanager           = "http://localhost:4566"
    serverlessrepo           = "http://localhost:4566"
    servicediscovery         = "http://localhost:4566"
    ses                      = "http://localhost:4566"
    sesv2                    = "http://localhost:4566"
    sns                      = "http://localhost:4566"
    sqs                      = "http://localhost:4566"
    ssm                      = "http://localhost:4566"
    stepfunctions            = "http://localhost:4566"
    sts                      = "http://localhost:4566"
    swf                      = "http://localhost:4566"
    timestreamwrite          = "http://localhost:4566"
    transfer                 = "http://localhost:4566"
    waf                      = "http://localhost:4566"
    wafv2                    = "http://localhost:4566"
    xray                     = "http://localhost:4566"
  }

  default_tags {
    tags = {
      Environment = "Local"
      Service     = "LocalStack"
    }
  }
}

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.100.0"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can test it is working by performing the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all went well we should see&lt;/p&gt;

&lt;h2&gt;
  
  
  terraform init
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "6.9.0"...
- Installing hashicorp/aws v6.9.0...
- Installed hashicorp/aws v6.9.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  terraform plan
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Protecting an API with AWS API Gateway and Auth0</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Fri, 15 Aug 2025 22:45:27 +0000</pubDate>
      <link>https://forem.com/darkedges/protecting-an-api-with-aws-api-gateway-and-auth0-5ggb</link>
      <guid>https://forem.com/darkedges/protecting-an-api-with-aws-api-gateway-and-auth0-5ggb</guid>
      <description>&lt;p&gt;You have an API that you would like to protect with an OAuth Access Token from Auth0 using AWS API Gateway. Sounds simple enough, so lets do this using a local development environment thats hosts &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API - Deployed as a simple Go API Services using AWS Fargate&lt;/li&gt;
&lt;li&gt;API Gateway - Deployed using API Gateway V2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now this normally would require creating an AWS Account, but we are going to be using LocalStack which provide those services (and more) in a container that can be deployed locally. This way we can learn AWS without the costs (its not much anyway) by deploying the services via Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;We are assuming that you have the following deployed locally&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Visual Studio Code&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will also need to sign up at &lt;a href="https://app.localstack.cloud/sign-up" rel="noopener noreferrer"&gt;https://app.localstack.cloud/sign-up&lt;/a&gt; to get an API Key that we need to run this stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signup and New Instance
&lt;/h2&gt;

&lt;p&gt;The following outlines the basic steps to get a localstack instance registered&lt;/p&gt;

&lt;h3&gt;
  
  
  Signup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://app.localstack.cloud/sign-up" rel="noopener noreferrer"&gt;https://app.localstack.cloud/sign-up&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Provide an email address and click &lt;code&gt;Continue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Provide a password and click &lt;code&gt;Continue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Provide your details and click &lt;code&gt;Start Trial&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check your email and follow the instrucions provided to &lt;code&gt;Activate Account&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When completed login using the details your provided earlier and click &lt;code&gt;Continue&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Get Personal Auth Token
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://app.localstack.cloud/settings/auth-tokens" rel="noopener noreferrer"&gt;https://app.localstack.cloud/settings/auth-tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Under Personal Auth Token click the &lt;code&gt;Copy&lt;/code&gt; icon.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deploying LocalStack
&lt;/h2&gt;

&lt;p&gt;Create a file &lt;code&gt;docker-compose.yaml&lt;/code&gt; with the following and replace &lt;code&gt;&amp;lt;copy PAT here&amp;gt;&lt;/code&gt; with your PAT you copied earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  localstack:
    container_name: "lsa-localstack"
    image: localstack/localstack-pro:latest
    environment:
      LOCALSTACK_AUTH_TOKEN: &amp;lt;copy PAT here&amp;gt;
    ports:
      - "127.0.0.1:4566:4566"
      - "127.0.0.1:4510-4559:4510-4559"
      - "127.0.0.1:443:443"
    volumes:
      - "localstack:/var/lib/localstack:rw"
      - "/var/run/docker.sock:/var/run/docker.sock"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s
    networks:
      localstack:
        aliases:
          - localstack
volumes:
  localstack:
networks:
  localstack:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and deploy it using &lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can confirm it is working by going to &lt;a href="https://app.localstack.cloud/inst/default/status" rel="noopener noreferrer"&gt;https://app.localstack.cloud/inst/default/status&lt;/a&gt; and the services should be marked as &lt;code&gt;available&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;

&lt;p&gt;You can install a version of the AWS CLI that works with localstack by reviewing &lt;a href="https://docs.localstack.cloud/aws/integrations/aws-native-tools/aws-cli/" rel="noopener noreferrer"&gt;https://docs.localstack.cloud/aws/integrations/aws-native-tools/aws-cli/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That concludes this part. Next we configure Terraform and deploy our API Service into AWS Fargate.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>aws</category>
      <category>containers</category>
    </item>
    <item>
      <title>kind, wsl2 and multus</title>
      <dc:creator>DarkEdges</dc:creator>
      <pubDate>Tue, 22 Apr 2025 12:50:08 +0000</pubDate>
      <link>https://forem.com/darkedges/kind-wsl2-and-multus-5b9p</link>
      <guid>https://forem.com/darkedges/kind-wsl2-and-multus-5b9p</guid>
      <description>&lt;p&gt;All we wanted to do was install &lt;code&gt;kubevirt&lt;/code&gt; create 3 Windows 2022 servers and have them talk to one another over a static IP Address. Simple we thought, Just tell the Virtual Machine to use a host-only IP Address and we are in the money.&lt;/p&gt;

&lt;p&gt;Firstly you cannot assign a second network interface unless it is another CNI implementation and secondly you need to install more black magic to make it work.&lt;/p&gt;

&lt;p&gt;That black magic is &lt;a href="https://github.com/k8snetworkplumbingwg/multus-cni" rel="noopener noreferrer"&gt;https://github.com/k8snetworkplumbingwg/multus-cni&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0 - Install WSL2 and AlmaLinux 9 with docker
&lt;/h2&gt;

&lt;p&gt;We will write a seperate guide on how to do this and update this one with a link to it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Install kind
&lt;/h2&gt;

&lt;p&gt;We are using &lt;code&gt;kind&lt;/code&gt; as it is a supported version for &lt;code&gt;kubevirt&lt;/code&gt; so we do that by creating a config file and using it in the deployment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kind.x-k8s.io/v1alpha4&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;Cluster&lt;/span&gt;
&lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;control-plane&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create the cluster using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kind create cluster --name kubevirt --config=kind_config.yaml
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 - Install Multus Daemonset
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 - Install CNI plugins
&lt;/h2&gt;

&lt;p&gt;First find the name of control plane&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kubectl get nodes 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which returns &lt;code&gt;kubevirt-control-plane&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;NAME                     STATUS   ROLES           AGE   VERSION
kubevirt-control-plane   Ready    control-plane   51m   v1.32.2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then connect to the control plane&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker exec -it kubevirt-control-plane sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to allow us to install the missing CNI Plugins&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cd /tmp
curl -L -o cni-plugins.tgz https://github.com/containernetworking/plugins/releases/download/v1.6.2/cni-plugins-linux-amd64-v1.6.2.tgz
tar -C /opt/cni/bin -xzf cni-plugins.tgz
rm -rf cni-plugins.tgz
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 create a NetworkAttachmentDefinition
&lt;/h2&gt;

&lt;p&gt;This is done through the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cat &amp;lt;&amp;lt;EOF | kubectl create -f -
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: ipvlan-def
spec:
  config: '{
      "cniVersion": "0.3.1",
      "type": "ipvlan",
      "master": "eth0",
      "mode": "l2",
      "ipam": {
        "type": "host-local",
        "subnet": "192.168.200.0/24",
        "rangeStart": "192.168.200.201",
        "rangeEnd": "192.168.200.205",
        "gateway": "192.168.200.1"
      }
    }'
EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5 - Create some Pods with additional network interfaces
&lt;/h2&gt;

&lt;p&gt;The following creates&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 pods with additional Network Interfaces listing on

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;192.168.200.201&lt;/code&gt; - &lt;code&gt;multus-alpine&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;192.168.200.202&lt;/code&gt; - &lt;code&gt;multus-alpine-2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;192.168.200.203&lt;/code&gt; - &lt;code&gt;net-pod&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cat &amp;lt;&amp;lt;EOF | kubectl create -f -
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: multus-alpine
  name: multus-alpine
  namespace: default
  annotations:
    k8s.v1.cni.cncf.io/networks: '[
      {
         "name" : "ipvlan-def",
         "interface": "eth1",
         "ips": ["192.168.200.201"]
      }
    ]'
spec:
  containers:
    - name: multus-alpine
      image: jmalloc/echo-server
  restartPolicy: Always
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: multus-alpine-2
  name: multus-alpine-2
  namespace: default
  annotations:
    k8s.v1.cni.cncf.io/networks: '[
      {
         "name" : "ipvlan-def",
         "interface": "eth1",
        "ips": ["192.168.200.202"]
      }
    ]'
spec:
  containers:
    - name: multus-alpine
      image: jmalloc/echo-server
  restartPolicy: Always
---
apiVersion: v1
kind: Pod
metadata:
  name: net-pod
  annotations:
    k8s.v1.cni.cncf.io/networks: '[
      {
         "name" : "ipvlan-def",
         "interface": "eth1",
        "ips": ["192.168.200.203"]
      }
    ]'
spec:
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6 - Test connectivity
&lt;/h2&gt;

&lt;p&gt;Check that we are getting responses from both &lt;code&gt;multus-alpine&lt;/code&gt; and &lt;code&gt;multus-alpine-2&lt;/code&gt; from the &lt;code&gt;net-pod&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  multus-alpine
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kubectl exec -it net-pod -- curl http://192.168.200.201:8080/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Request served by multus-alpine

GET / HTTP/1.1

Host: 192.168.200.201:8080
Accept: */*
User-Agent: curl/8.7.1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  multus-alpine-2
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kubectl exec -it net-pod -- curl http://192.168.200.202:8080/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Request served by multus-alpine-2

GET / HTTP/1.1

Host: 192.168.200.202:8080
Accept: */*
User-Agent: curl/8.7.1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>kubernetes</category>
      <category>multus</category>
      <category>microsoft</category>
    </item>
  </channel>
</rss>
