<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The Cloud Dude]]></title><description><![CDATA[I speak about GoLang and the Cloud on this blog, and sometimes some Terraform too.]]></description><link>https://theclouddude.co.uk</link><generator>RSS for Node</generator><lastBuildDate>Sat, 11 Apr 2026 08:34:33 GMT</lastBuildDate><atom:link href="https://theclouddude.co.uk/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Making Pulumi Feel Like Terraform: A Python Developer's Guide]]></title><description><![CDATA[If you have been using Terraform for more than five minutes, you would have come across variables and replacing default values for instances using a .tfvars file. Below is a snippet of replacing default values for an EC2 Instance using Terraform:
ec2...]]></description><link>https://theclouddude.co.uk/making-pulumi-feel-like-terraform-a-python-developers-guide</link><guid isPermaLink="true">https://theclouddude.co.uk/making-pulumi-feel-like-terraform-a-python-developers-guide</guid><category><![CDATA[Pulumi]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[Python]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Infrastructure as code]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 24 Jan 2025 12:41:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737722340216/54e9d8c9-83b2-407f-921c-85e93a3f8925.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you have been using Terraform for more than five minutes, you would have come across variables and replacing default values for instances using a .tfvars file. Below is a snippet of replacing default values for an EC2 Instance using Terraform:</p>
<pre><code class="lang-python">ec2_instances = [
  {
    name        = <span class="hljs-string">"jc-ec2-app001"</span>
    role        = <span class="hljs-string">"web"</span>
    size        = <span class="hljs-string">"t3.micro"</span>
    application = <span class="hljs-string">"app"</span>
    ami         = <span class="hljs-string">"ami-008687c5b5546727c"</span>
    family      = <span class="hljs-string">"windows"</span>
    volumes = [
      {
        device_name           = <span class="hljs-string">"/dev/sda1"</span>
        size                  = <span class="hljs-number">30</span>
        volume_type           = <span class="hljs-string">"gp3"</span>
        iops                  = <span class="hljs-number">3000</span>
        throughput            = <span class="hljs-number">125</span>
        encrypted             = true
        delete_on_termination = true
      },
      {
        device_name           = <span class="hljs-string">"/dev/xvdf"</span>
        size                  = <span class="hljs-number">20</span>
        volume_type           = <span class="hljs-string">"gp3"</span>
        iops                  = <span class="hljs-number">3000</span>
        throughput            = <span class="hljs-number">125</span>
        encrypted             = false
        delete_on_termination = true
      }
    ]
  },
]
</code></pre>
<p>The first thing I noticed when using Pulumi with Python is that you don’t have the option of setting up a resource with default values and then passing through new values from the yaml file to replace those default values and essentially getting Pulumi to work in a Terraform way.  </p>
<p>This is where I build on the dictionary work I wrote about an article ago. If you want to take a look, check out the following link: <a target="_blank" href="https://theclouddude.co.uk/playing-around-with-dictionaries-in-python">Playing Around with Dictionaries in Python</a>  </p>
<p>For this example, I will make an AWS backup plan with default values and show how we can replace those with new ones from a yaml file.  </p>
<p>I first created my arguments class, where I will pass through the new values I wish to replace. I have already put default values into that argument class under a variable called. <code>backup_configs</code> Of type dictionary. This Class will be the Argument class that will be passed on to the <code>BackupPlanCreator</code><br />Class, which will do the heavy lifting of making the AWS Backup Vault and the AWS Backup Plan.</p>
<p>A snippet of the code for the argument class is here:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BackupPlanCreatorArgs</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">
        self,
        name: str,
        vault_name: str,
        base_tags: dict,
    </span>):</span>
        self.name = name
        self.vault_name = vault_name
        self.base_tags = base_tags

        self.backup_configs = {
            <span class="hljs-string">"hourly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-hourly-ret7-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"hourly-backup-rule"</span>,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 * * * ? *)"</span>,
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">7</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"recovery_point_tags"</span>: {
                    <span class="hljs-string">"aws_profile"</span>: <span class="hljs-string">"jc-profile"</span>,
                    <span class="hljs-string">"aws_region"</span>: <span class="hljs-string">"us-east-2"</span>,
                    <span class="hljs-string">"type"</span>: <span class="hljs-string">"hourly-backup"</span>,
                },
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"daily"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-daily-ret14-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"daily-backup-rule"</span>,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 5 ? * * *)"</span>,
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">30</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">125</span>,
                <span class="hljs-string">"recovery_point_tags"</span>: {
                    <span class="hljs-string">"aws_profile"</span>: <span class="hljs-string">"jc-profile"</span>,
                    <span class="hljs-string">"aws_region"</span>: <span class="hljs-string">"us-east-2"</span>,
                    <span class="hljs-string">"type"</span>: <span class="hljs-string">"daily-backup"</span>,
                },
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"weekly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-weekly-ret365-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"weekly-backup-rule"</span>,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 6 ? * 2 *)"</span>,
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">365</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"recovery_point_tags"</span>: {
                    <span class="hljs-string">"aws_profile"</span>: <span class="hljs-string">"jc-profile"</span>,
                    <span class="hljs-string">"aws_region"</span>: <span class="hljs-string">"us-east-2"</span>,
                    <span class="hljs-string">"type"</span>: <span class="hljs-string">"weekly-backup"</span>,
                },
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"monthly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-monthly-ret365-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"monthly-backup-rule"</span>,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 6 1 * ? *)"</span>,
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">365</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"recovery_point_tags"</span>: {
                    <span class="hljs-string">"aws_profile"</span>: <span class="hljs-string">"jc-profile"</span>,
                    <span class="hljs-string">"aws_region"</span>: <span class="hljs-string">"us-east-2"</span>,
                    <span class="hljs-string">"type"</span>: <span class="hljs-string">"monthly-backup"</span>,
                },
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
        }
</code></pre>
<p>I wrote the following code into the class, which will replace values in what is essentially a nested dictionary with multiple values. I will go through this code and explain what it does.  </p>
<pre><code class="lang-python">        (<span class="hljs-number">1</span>) self.final_backup_config = self.backup_configs.copy()

           (<span class="hljs-number">2</span>) <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> new_backup_config_values.items():
                <span class="hljs-keyword">if</span> (
                   (<span class="hljs-number">3</span>) isinstance(value, dict) <span class="hljs-keyword">and</span> key <span class="hljs-keyword">in</span> self.final_backup_config
                ):  <span class="hljs-comment"># isinstance() checks to see if it has been passed a dictionary.</span>
                    print(<span class="hljs-string">"Found BackUp Config Dictionary Merging Values"</span>)
                   (<span class="hljs-number">4</span>) <span class="hljs-keyword">for</span> nested_key, nested_value <span class="hljs-keyword">in</span> value.items():
                        self.final_backup_config[key][nested_key] = nested_value
                <span class="hljs-keyword">else</span>:
               (<span class="hljs-number">5</span>)     print(<span class="hljs-string">"Do not have any new values keeping BackUp Config the same."</span>)
</code></pre>
<ol>
<li><p>Here, I take a copy of the <code>backup_configs</code> variable values. This goes back to my GoLang days; when passing a variable around, Go will make a copy of that variable so you still have the original values if you want to call them later. We will do more on that later.</p>
</li>
<li><p>The <code>items()</code> method I found to be quite clever in Python. Essentially, you can pass a whole dictionary to <code>items()</code> It will return a view object that displays a list of a dictionary’s key-value tuple pairs. Which is precisely what I needed for this project. To take that one step further and start manipulating that data, you can put the <code>items()</code> method into a <code>for</code> loop. I thought this was quite cool and did not require me to start looking at programming algorithms and tricks to manipulate this data.</p>
</li>
<li><p>The <code>isinstance()</code> the function I added because I am double-checking my code here, again, a nifty function that basically checks before continuing that we are working with a dictionary. Its because of this function that you could essentially put all of this into a module or a function, it allows your code to start becoming quite dynamic and perhaps called elsewhere in your project. I will need to do this further down the line.</p>
</li>
<li><p><code>for nested_key, nested_value in value.items():</code> As we are working with a nested dictionary, I need to access the nested value of the dictionary, as this is the actual value that the user will change.</p>
</li>
<li><p><code>print("Do not have any new values keeping BackUp Config the same.")</code>. Now, say that we have passed no changed values to this whole <code>for</code> loop. We then need a way to skip through and keep our values. When I first wrote this <code>if/else</code> statement, I thought it would work with changing defaults and that I would not have to write anything more. I thought I had finished the piece of code. Yet I later found that this code is step one in about five more steps to make it all optional.</p>
</li>
</ol>
<p>Right now, we have something that we can pass from the yaml to manipulate the dictionary. If you were to do this every time and not keep your default values, then this would be all you need to do. Yet that’s not very Terraformy. Terraform allows you to either keep the values or change them. Therefore, to achieve that functionality, we need to do more work. Onwards!!</p>
<pre><code class="lang-python">        (<span class="hljs-number">1</span>) <span class="hljs-keyword">if</span> new_backup_config_values <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
                <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> new_backup_config_values.items():
                    <span class="hljs-keyword">if</span> (
                        isinstance(value, dict) <span class="hljs-keyword">and</span> key <span class="hljs-keyword">in</span> self.final_backup_config
                    ):  <span class="hljs-comment"># isinstance() checks to see if it has been passed a dictionary.</span>
                        print(<span class="hljs-string">"Found BackUp Config Dictionary Merging Values"</span>)
                        <span class="hljs-keyword">for</span> nested_key, nested_value <span class="hljs-keyword">in</span> value.items():
                            self.final_backup_config[key][nested_key] = nested_value
        (<span class="hljs-number">2</span>) <span class="hljs-keyword">else</span>:
                print(<span class="hljs-string">"Do not have any new values keeping BackUp Config the same."</span>)
</code></pre>
<ol>
<li><p><code>if new_backup_config_values is not None:</code> The first part we need to put into all of this is an option to handle type <code>None</code>. This is because in our <code>main.py</code> the Pulumi function <code>pulumi.Config().get("backupConfigs"</code>) reads from the Yaml file will sometimes have values, or it will return <code>None</code> for no values. Therefore, we need to handle that here.</p>
</li>
<li><p><code>else:</code> If the function returns no values, then basically, we are keeping our defaults and handing them over to the <code>BackupPlanCreator</code> Class.</p>
</li>
</ol>
<p>Yet, there is still one issue with our code. Our <code>BackupPlanCreatorArgs</code> Class does have a parameter that expects to receive the changed default values. That parameter is called:  <code>new_backup_config_values,</code> If you remember rightly from earlier, this is the foundation for our whole <code>for</code> loop. Yet the idea is sometimes we will pass on new values, and sometimes we will not.  </p>
<p>How to solve this one…</p>
<pre><code class="lang-python">(<span class="hljs-number">1</span>) backup_configs_yaml = pulumi.Config().get(<span class="hljs-string">"backupConfigs"</span>)
  (<span class="hljs-number">2</span>) <span class="hljs-keyword">if</span> backup_configs_yaml == <span class="hljs-literal">None</span>:
            backup_creator_args = backup.BackupPlanCreatorArgs(
                 name=<span class="hljs-string">"backupPlanCreator"</span>,
                 vault_name=vault_name,
             (<span class="hljs-number">3</span>) new_backup_config_values=<span class="hljs-literal">None</span>,
                 base_tags=base_tags,
            )
(<span class="hljs-number">4</span>) <span class="hljs-keyword">else</span>:
      (<span class="hljs-number">5</span>)  new_backup_config_values = yaml.safe_load(
            backup_configs_yaml
        )  <span class="hljs-comment"># This safe loads yaml without Embedding Json and having future issues.</span>
        backup_creator_args = backup.BackupPlanCreatorArgs(
            name=<span class="hljs-string">"backupPlanCreator"</span>,
            vault_name=vault_name,
        (<span class="hljs-number">6</span>) new_backup_config_values=new_backup_config_values,
            base_tags=base_tags,
        )
</code></pre>
<ol>
<li><p><code>backup_configs_yaml = pulumi.Config().get("backupConfigs")</code>: As described earlier, this function is the key to it all. It will either pass the values that we are changing or will pass the type <code>None</code></p>
</li>
<li><p><code>if backup_configs_yaml == None:</code> Once I figured that out, I could start building logic around that function and of passing type <code>None</code></p>
</li>
<li><p><code>new_backup_config_values=None,</code> If none is passed from the yaml file, then within the <code>if</code> statement logic, I am simply going to call the <code>args</code> Class and pass <code>None</code>. Taking a copy of <code>backup_configs</code> now allows us to pass the original variable through the <code>for</code> loop logic and to the <code>BackupPlanCreator</code> class with no changes. If we had not taken a copy earlier, our lives would have been made a bit harder, and new code would have had to be written to handle this outcome.</p>
</li>
<li><p><code>else:</code> Well, this becomes self-explanatory, really, and perhaps I am teaching to suck eggs, but if the function <code>pulumi.Config().get("backupConfigs")</code> does pass values, then the code below will be executed.</p>
</li>
<li><p><code>new_backup_config_values = yaml.safe_load (backup_configs_yaml)</code>: This will safely load the yaml into the class and allow our <code>For</code> loop with the <code>items()</code> method to go through and read the new values. I liked this <code>yaml.safeload()</code> function because it means I don’t have to try and put <code>json</code> formatted code into <code>YAML</code> anyone who has been doing this for some time would know that’s an absolute headache. It also keeps my <code>Yaml</code> file very <code>yaml</code> code-standard and easy to read.</p>
</li>
<li><p><code>new_backup_config_values=new_backup_config_values</code> This passes our new variable values into the class.</p>
</li>
</ol>
<p>Here is a snippet of <code>Yaml</code> code for reference.</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">backupConfigs:</span>
    <span class="hljs-attr">hourly:</span>
      <span class="hljs-attr">enable_windows_vss:</span> <span class="hljs-literal">false</span>
</code></pre>
<p>That's how you can use Python to build Terraform-like behaviour into Pulumi and make your code more usable for future engineers.  </p>
<p>I hope this has been helpful. Until next time,  </p>
<p>Happy Coding,</p>
<p>The Cloud Dude.</p>
]]></content:encoded></item><item><title><![CDATA[Playing Around With Dictionaries in Python]]></title><description><![CDATA[For work, I have had an interesting project where I had to pass through multiple AWS Backup Plans into a Python class to make all my Backup Plans; these backup plans had so many options that needed configuring that the most logical way of passing the...]]></description><link>https://theclouddude.co.uk/playing-around-with-dictionaries-in-python</link><guid isPermaLink="true">https://theclouddude.co.uk/playing-around-with-dictionaries-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Pulumi]]></category><category><![CDATA[#IaC]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 06 Dec 2024 16:45:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733503395387/d3a8a7ef-e9d4-4b17-a147-dd948a42d9c7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For work, I have had an interesting project where I had to pass through multiple AWS Backup Plans into a Python class to make all my Backup Plans; these backup plans had so many options that needed configuring that the most logical way of passing the variables through was with a dictionary. It's a bit like a <code>Struct</code> in GoLang.</p>
<p>When approaching this task, the first milestone was to get Python to read through the dictionary. I did this by hardcoding the dictionaries into my Python class and choosing a toggle to select that dictionary to see if Python was reading my dictionaries correctly.  </p>
<p>The code below shows the passing of the toggle <code>backup_type</code> and the hard coding of the dictionary.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BackupPlanCreator</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, vault_name: str, base_tags: dict, backup_type: str</span>):</span>
        self.vault_name = vault_name
        self.base_tags = base_tags
        self.backup_type = backup_type

        <span class="hljs-comment"># Create the backup vault</span>
        self.backup_vault = aws.backup.Vault(
            resource_name=<span class="hljs-string">"backupVault"</span>,  <span class="hljs-comment"># Unique resource name</span>
            name=self.vault_name,  <span class="hljs-comment"># Actual vault name</span>
            tags=self.base_tags,
        )

        <span class="hljs-comment"># Define configurations for each backup type</span>
        self.backup_configs = {
            <span class="hljs-string">"hourly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-hourly-ret7-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"hourly-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 * * * ? *)"</span>,  <span class="hljs-comment"># Every hour</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">7</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"daily"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-daily-ret14-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"daily-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 5 ? * * *)"</span>,  <span class="hljs-comment"># Daily at 5 AM</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">30</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"weekly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-weekly-ret365-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"weekly-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 6 1 * ? *)"</span>,  <span class="hljs-comment"># Every Monday at 5 AM</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">365</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"monthly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-monthly-ret365-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"monthly-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 6 1 * ? *)"</span>,  <span class="hljs-comment"># 1st of every month at 5 AM</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">365</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
        }
</code></pre>
<p>Below is the same class reading through the dictionary and compiling the <code>backup_rules</code> for the ultimate plan.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Get the configuration for the specified backup type</span>
        config = self.backup_configs.get(self.backup_type)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> config:
            <span class="hljs-keyword">raise</span> ValueError(
                <span class="hljs-string">"Invalid backup type specified. Choose from: 'hourly', 'daily', 'weekly', 'monthly'."</span>
            )

        <span class="hljs-comment"># Define the backup rules using the configuration from the dictionary</span>
        backup_rules = [
            {
                <span class="hljs-string">"rule_name"</span>: config[<span class="hljs-string">"rule_name"</span>],
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"completion_window"</span>: config[<span class="hljs-string">"completion_window"</span>],
                <span class="hljs-string">"copy_actions"</span>: [
                    {
                        <span class="hljs-string">"destination_vault_arn"</span>: self.backup_vault.arn,
                        <span class="hljs-string">"lifecycle"</span>: {
                            <span class="hljs-string">"cold_storage_after"</span>: <span class="hljs-number">0</span>,
                            <span class="hljs-string">"delete_after"</span>: config[<span class="hljs-string">"delete_after_days"</span>],
                            <span class="hljs-string">"opt_in_to_archive_for_supported_resources"</span>: <span class="hljs-literal">False</span>,
                        },
                    }
                ],
                <span class="hljs-string">"enable_continuous_backup"</span>: <span class="hljs-literal">False</span>,
                <span class="hljs-string">"lifecycle"</span>: {
                    <span class="hljs-string">"cold_storage_after"</span>: <span class="hljs-number">0</span>,
                    <span class="hljs-string">"delete_after"</span>: config[<span class="hljs-string">"delete_after_days"</span>],
                    <span class="hljs-string">"opt_in_to_archive_for_supported_resources"</span>: <span class="hljs-literal">False</span>,
                },
                <span class="hljs-string">"schedule"</span>: config[<span class="hljs-string">"schedule"</span>],
                <span class="hljs-string">"schedule_expression_timezone"</span>: <span class="hljs-string">"UTC"</span>,
                <span class="hljs-string">"start_window"</span>: config[<span class="hljs-string">"start_window"</span>],
            }
        ]

        <span class="hljs-comment"># Define the advanced backup settings</span>
        advanced_backup_settings = [
            {
                <span class="hljs-string">"backup_options"</span>: {
                    <span class="hljs-string">"WindowsVSS"</span>: (
                        <span class="hljs-string">"enabled"</span> <span class="hljs-keyword">if</span> config[<span class="hljs-string">"enable_windows_vss"</span>] <span class="hljs-keyword">else</span> <span class="hljs-string">"disabled"</span>
                    ),
                },
                <span class="hljs-string">"resource_type"</span>: <span class="hljs-string">"EC2"</span>,
            }
        ]
</code></pre>
<p>As you can see, a <code>for</code> loop is not currently needed, as I am only seeing if Python can read through and select the correct dictionary. I then pass <code>backup_type</code> to the Python Backup Plan Function as a string. This one is <code>daily</code> to choose the <code>daily</code> backup plan out of the dictionary.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Create the backup plan</span>
        backup_creator = backup.BackupPlanCreator(vault_name, base_tags, <span class="hljs-string">"daily"</span>)
</code></pre>
<p>This is great and shows me that I can process a dictionary. However, I want to take this a step further and pass the dictionary around. To achieve this, I moved the dictionary out of the class and into a variable to see if I could pass it through and get the same result.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> jc_aws_backup <span class="hljs-keyword">as</span> backup

<span class="hljs-comment"># Define configurations for each backup type</span>
   backup_configs = {
            <span class="hljs-string">"hourly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-hourly-ret7-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"hourly-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 * * * ? *)"</span>,  <span class="hljs-comment"># Every hour</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">7</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"daily"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-daily-ret14-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"daily-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 5 ? * * *)"</span>,  <span class="hljs-comment"># Daily at 5 AM</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">30</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">125</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"weekly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-weekly-ret365-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"weekly-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 6 1 * ? *)"</span>,  <span class="hljs-comment"># Every Monday at 5 AM</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">365</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
            <span class="hljs-string">"monthly"</span>: {
                <span class="hljs-string">"name"</span>: <span class="hljs-string">"bkp-monthly-ret365-jc"</span>,
                <span class="hljs-string">"rule_name"</span>: <span class="hljs-string">"monthly-backup-rule"</span>,
                <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                <span class="hljs-string">"schedule"</span>: <span class="hljs-string">"cron(0 6 1 * ? *)"</span>,  <span class="hljs-comment"># 1st of every month at 5 AM</span>
                <span class="hljs-string">"delete_after_days"</span>: <span class="hljs-number">365</span>,
                <span class="hljs-string">"start_window"</span>: <span class="hljs-number">60</span>,
                <span class="hljs-string">"completion_window"</span>: <span class="hljs-number">120</span>,
                <span class="hljs-string">"enable_windows_vss"</span>: <span class="hljs-literal">True</span>,
            },
        }

<span class="hljs-comment"># Create the backup plan</span>
        backup_creator = backup.BackupPlanCreator(vault_name, base_tags, backup_configs, <span class="hljs-string">"daily"</span>)
</code></pre>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CorityBackupPlanCreator</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">
        self, vault_name: str, base_tags: dict, backup_configs: dict, backup_type: str
    </span>):</span>
        self.base_tags = base_tags
        self.backup_type = backup_type
        self.backup_configs = backup_configs

        <span class="hljs-comment"># Create the backup vault</span>
        self.backup_vault = aws.backup.Vault(
            vault_name,
            tags=self.base_tags,
        )

        config = self.backup_configs.get(self.backup_type)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> config:
            <span class="hljs-keyword">raise</span> ValueError(
                <span class="hljs-string">"Invalid backup type specified. Choose from: 'hourly', 'daily', 'weekly', 'monthly'."</span>
            )
        <span class="hljs-comment"># Define the backup rules using the configuration from the dictionary</span>
        backup_rules = []
        <span class="hljs-keyword">for</span> _, backup_details <span class="hljs-keyword">in</span> self.backup_configs.items():
            backup_rules.append(
                aws.backup.PlanRuleArgs(
                    rule_name=backup_details[<span class="hljs-string">"rule_name"</span>],
                    target_vault_name=self.backup_vault.name,
                    schedule=backup_details[<span class="hljs-string">"schedule"</span>],
                    lifecycle=aws.backup.PlanRuleLifecycleArgs(
                        delete_after=backup_details[<span class="hljs-string">"delete_after_days"</span>]
                    ),
                    start_window=backup_details[<span class="hljs-string">"start_window"</span>],
                    completion_window=backup_details[<span class="hljs-string">"completion_window"</span>],
                    recovery_point_tags=self.base_tags,
                )
            )

        <span class="hljs-comment"># Define the advanced backup settings</span>
        advanced_backup_settings = []
        <span class="hljs-keyword">for</span> _, backup_details <span class="hljs-keyword">in</span> self.backup_configs.items():
            advanced_backup_settings.append(
                aws.backup.PlanAdvancedBackupSettingArgs(
                    backup_options={
                        <span class="hljs-string">"WindowsVSS"</span>: (
                            <span class="hljs-string">"enabled"</span>
                            <span class="hljs-keyword">if</span> backup_details[<span class="hljs-string">"enable_windows_vss"</span>]
                            <span class="hljs-keyword">else</span> <span class="hljs-string">"disabled"</span>
                        )
                    },
                    resource_type=<span class="hljs-string">"EC2"</span>,
                )
            )

        <span class="hljs-comment"># Create the backup plan</span>
        self.plan_resource = aws.backup.Plan(
            resource_name=backup_details[<span class="hljs-string">"rule_name"</span>],
            rules=backup_rules,
            advanced_backup_settings=advanced_backup_settings,
            tags=self.base_tags,
        )

        <span class="hljs-comment"># Export the backup plan ID</span>
        pulumi.export(<span class="hljs-string">"backupPlanId"</span>, self.plan_resource.id)
</code></pre>
<p>What I also did here was add a <code>for</code> loop because my logic will change later, and I will want all four backup plans made at once from the dictionary. Yet this showed me that I could pass around a dictionary, and If I could pass around a dictionary, that meant I could also make it come from the central pulumi.yaml file and load it as an <code>json</code> object into the class. This is because when working with Pulumi, the idea you want to get to is to pass everything through one central yaml file that is linked to each environment stack. That is always the end goal. I then changed my main.py code around a bit like this to achieve this.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> json 
<span class="hljs-keyword">import</span> jc_aws_backup <span class="hljs-keyword">as</span> backup

backup_configs_json = pulumi.Config().require(<span class="hljs-string">"backupConfigs"</span>)
vault_name = <span class="hljs-string">f"bkp-vault-<span class="hljs-subst">{project_profile}</span>-<span class="hljs-subst">{project_short_region}</span>"</span>[:<span class="hljs-number">42</span>]

<span class="hljs-comment"># Load the Backup configs from the Yaml and pass as a Json String.</span>
<span class="hljs-comment"># Do this with Error Handling so you can see where in JSON it is failing.</span>
<span class="hljs-keyword">try</span>:
    backup_configs = json.loads(backup_configs_json)
<span class="hljs-keyword">except</span> json.JSONDecodeError <span class="hljs-keyword">as</span> e:
    <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid JSON in backupConfigs: <span class="hljs-subst">{e}</span>"</span>)

<span class="hljs-comment"># Create the backup plan</span>
backup_creator = backup.BackupPlanCreator(
    vault_name=vault_name,
    base_tags=base_tags,
    backup_type=<span class="hljs-string">"daily"</span>
    backup_configs=backup_configs,
)
</code></pre>
<p>I also added some Error Handling. This is because when I first passed through the <code>json</code> from the <code>yaml</code> file, I got a panic error shown below, and it was a pretty unhelpful error in figuring out where in the <code>json</code> it was failing.</p>
<pre><code class="lang-bash">Diagnostics:
  pulumi:pulumi:Stack (jcontent-dev-v2-jcontent_dev_v2):
    error: Program failed with an unhandled exception:
    Traceback (most recent call last):
      File <span class="hljs-string">"C:\DevOps\infrastructure.pulumi\iac-development\jcontent-dev-v2\__main__.py"</span>, line 70, <span class="hljs-keyword">in</span> &lt;module&gt;
        backup_configs = json.loads(backup_configs_json)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File <span class="hljs-string">"C:\Python312\Lib\json\__init__.py"</span>, line 346, <span class="hljs-keyword">in</span> loads
        <span class="hljs-built_in">return</span> _default_decoder.decode(s)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
      File <span class="hljs-string">"C:\Python312\Lib\json\decoder.py"</span>, line 337, <span class="hljs-keyword">in</span> decode
        obj, end = self.raw_decode(s, idx=_w(s, 0).end())
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File <span class="hljs-string">"C:\Python312\Lib\json\decoder.py"</span>, line 355, <span class="hljs-keyword">in</span> raw_decode
        raise JSONDecodeError(<span class="hljs-string">"Expecting value"</span>, s, err.value) from None
    json.decoder.JSONDecodeError: Expecting value: line 23 column 27 (char 602)
</code></pre>
<p>By creating my Error handling, I get it to show me where exactly in the <code>json</code> object it is failing.<br />I have also supplied the <code>yaml</code> file below to show you how to pass in the object. This stumped me for a little while, and having an example would have helped.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">encryptionsalt:</span> <span class="hljs-string">VALUE</span>
<span class="hljs-attr">config:</span> 
<span class="hljs-attr">aws:region:</span> <span class="hljs-string">us-east-2</span>
<span class="hljs-attr">environment:</span> <span class="hljs-string">jc_testing_pulumi</span>
<span class="hljs-attr">backupConfigs:</span> <span class="hljs-string">|
    {
      "hourly": {
        "name": "bkp-hourly-ret7-jc",
        "rule_name": "hourly-backup-rule",
        "schedule": "cron(0 * * * ? *)",
        "delete_after_days": 7,
        "start_window": 60,
        "completion_window": 120,
        "enable_windows_vss": true
      },
      "daily": {
        "name": "bkp-daily-ret14-jc",
        "rule_name": "daily-backup-rule",
        "schedule": "cron(0 5 ? * * *)",
        "delete_after_days": 30,
        "start_window": 60,
        "completion_window": 125,
        "enable_windows_vss": true
      },
      "weekly": {
        "name": "bkp-weekly-ret365-jc",
        "rule_name": "weekly-backup-rule",
        "schedule": "cron(0 6 ? * 2 *)",
        "delete_after_days": 365,
        "start_window": 60,
        "completion_window": 120,
        "enable_windows_vss": true
      },
      "monthly": {
        "name": "bkp-monthly-ret365-jc",
        "rule_name": "monthly-backup-rule",
        "schedule": "cron(0 6 1 * ? *)",
        "delete_after_days": 365,
        "start_window": 60,
        "completion_window": 120,
        "enable_windows_vss": true
      }
    }</span>
</code></pre>
<p>This now prepares me for my final hurdle and task; what I actually want to happen is for the backup plan class to make all four backup plans in one run and name them accordingly. How I achieved this was first to remove <code>backup_type</code> from my Class and to change my <code>for</code> loop around to include the <code>aws.backup.plan()</code> function. Yet, I still needed it to name each plan for me. The code below shows how I achieved this.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BackupPlanCreator</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, vault_name: str, base_tags: dict, backup_configs: dict</span>):</span>
        self.base_tags = base_tags
        self.backup_configs = backup_configs

        <span class="hljs-comment"># Create the backup vault</span>
        self.backup_vault = aws.backup.Vault(
            vault_name,
            tags=self.base_tags,
        )

        <span class="hljs-comment"># Loop through each backup configuration and create a separate backup plan</span>
        <span class="hljs-keyword">for</span> backup_name, backup_details <span class="hljs-keyword">in</span> self.backup_configs.items():
            <span class="hljs-comment"># Define the backup rules</span>
            backup_plan = aws.backup.Plan(
                resource_name=<span class="hljs-string">f"<span class="hljs-subst">{backup_name}</span>-backup-plan"</span>,
                rules=[
                    {
                        <span class="hljs-string">"rule_name"</span>: backup_details[<span class="hljs-string">"rule_name"</span>],
                        <span class="hljs-string">"target_vault_name"</span>: self.backup_vault.name,
                        <span class="hljs-string">"schedule"</span>: backup_details[<span class="hljs-string">"schedule"</span>],
                        <span class="hljs-string">"lifecycle"</span>: {
                            <span class="hljs-string">"delete_after"</span>: backup_details[<span class="hljs-string">"delete_after_days"</span>],
                        },
                        <span class="hljs-string">"start_window"</span>: backup_details[<span class="hljs-string">"start_window"</span>],
                        <span class="hljs-string">"completion_window"</span>: backup_details[<span class="hljs-string">"completion_window"</span>],
                        <span class="hljs-string">"recovery_point_tags"</span>: self.base_tags,
                    }
                ],
                <span class="hljs-comment"># Define the advanced backup settings</span>
                advanced_backup_settings=[
                    {
                        <span class="hljs-string">"backup_options"</span>: {
                            <span class="hljs-string">"WindowsVSS"</span>: (
                                <span class="hljs-string">"enabled"</span>
                                <span class="hljs-keyword">if</span> backup_details[<span class="hljs-string">"enable_windows_vss"</span>]
                                <span class="hljs-keyword">else</span> <span class="hljs-string">"disabled"</span>
                            ),
                        },
                        <span class="hljs-string">"resource_type"</span>: <span class="hljs-string">"EC2"</span>,
                    }
                ],
                tags=self.base_tags,
            )
</code></pre>
<p>The call to this function is similar to before, without the <code>backup_type</code> part being passed.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> json 
<span class="hljs-keyword">import</span> jc_aws_backup <span class="hljs-keyword">as</span> backup

backup_configs_json = pulumi.Config().require(<span class="hljs-string">"backupConfigs"</span>)
vault_name = <span class="hljs-string">f"bkp-vault-<span class="hljs-subst">{project_profile}</span>-<span class="hljs-subst">{project_short_region}</span>"</span>[:<span class="hljs-number">42</span>]

<span class="hljs-comment"># Load the Backup configs from the Yaml and pass as a Json String.</span>
<span class="hljs-comment"># Do this with Error Handling so you can see where in JSON it is failing.</span>
<span class="hljs-keyword">try</span>:
    backup_configs = json.loads(backup_configs_json)
<span class="hljs-keyword">except</span> json.JSONDecodeError <span class="hljs-keyword">as</span> e:
    <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid JSON in backupConfigs: <span class="hljs-subst">{e}</span>"</span>)

<span class="hljs-comment"># Create the backup plan</span>
backup_creator = backup.BackupPlanCreator(
    vault_name=vault_name,
    base_tags=base_tags,
    backup_configs=backup_configs,
)
</code></pre>
<p>And that’s it. That’s how I have managed to pass around a dictionary and evolve that into an actual, useful <code>json</code> object to then make multiple plans for one backup plan run.</p>
<p>I will build on this dictionary now and put in some functionality that you see in Terraform. If you want to use default values, you can, or if you're going to override those values, you can. The thing with my dictionary right now is everything is being passed through, but essentially, the engineer will not want to do that. They will probably only want to configure certain parts of the dictionary from the yaml file.</p>
<p>Yet this is a cliffhanger moment for another blog post for another time.</p>
<p>Till next time, happy coding.</p>
]]></content:encoded></item><item><title><![CDATA[The Power of Input Validation with AWS and Python]]></title><description><![CDATA[Introduction
Managing AWS resources efficiently is a critical challenge for cloud architects and developers, especially using a dynamic language like Python. While Python offers flexibility and rapid development capabilities, its lack of built-in typ...]]></description><link>https://theclouddude.co.uk/the-power-of-input-validation-with-aws-and-python</link><guid isPermaLink="true">https://theclouddude.co.uk/the-power-of-input-validation-with-aws-and-python</guid><category><![CDATA[Python]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud Computing]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Tue, 19 Nov 2024 12:48:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732020294547/bd009bd8-d517-4a13-9f26-eed9593f7727.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Managing AWS resources efficiently is a critical challenge for cloud architects and developers, especially using a dynamic language like Python. While Python offers flexibility and rapid development capabilities, its lack of built-in type safety can lead to runtime errors that are difficult to debug. Inspired by Programming Design concepts from Will Kennedy's Go Lesson at GopherCon this year, this article explores how implementing robust input validation can mitigate these issues, offering a clearer, more maintainable approach to developing AWS infrastructure with Pulumi and Python.</p>
<h2 id="heading-why-input-validation-matters">Why Input Validation Matters</h2>
<h3 id="heading-error-prevention-and-pipeline-efficiency">Error Prevention and Pipeline Efficiency</h3>
<p>Input validation helps catch potential errors early in the development process, well before they reach production environments. By ensuring that inputs to your system—such as configuration variables for AWS resources—are of the correct type and format, you mitigate the risk of runtime errors that can disrupt services and degrade user experience. This preemptive approach reduces the need for rollbacks and hotfixes, enhancing the reliability of your deployment processes.</p>
<h3 id="heading-type-safety-in-python">Type Safety in Python</h3>
<p>Python's dynamic typing offers flexibility but lacks the inherent type safety of a language like Go. Implementing input validation through decorators can bring some of the benefits of static typing to Python, ensuring that functions receive arguments of the expected types.</p>
<h2 id="heading-implementing-input-validation-in-python">Implementing Input Validation in Python</h2>
<h3 id="heading-the-decorator-pattern">The Decorator Pattern</h3>
<p>The decorator pattern provides a simple yet powerful way to enforce input validation in Python. By wrapping class methods with a decorator, you can systematically check the types of arguments and raise descriptive errors when mismatches occur.</p>
<pre><code class="lang-python">
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CloudDudeFSxArgs</span>:</span>
<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">type_check_decorator</span>(<span class="hljs-params">func</span>):</span>
        <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">wrapper</span>(<span class="hljs-params">self, *args, **kwargs</span>):</span>
            <span class="hljs-comment"># Map positional arguments to their names</span>
            arg_names = [
                <span class="hljs-string">"create_active_directory"</span>,
                <span class="hljs-string">"service_name"</span>,
                <span class="hljs-string">"domain_name_suffix"</span>,
                <span class="hljs-string">"administrator_password"</span>,
                <span class="hljs-string">"directory_service_id"</span>,
                <span class="hljs-string">"deployment_type"</span>,
                <span class="hljs-string">"storage_type"</span>,
                <span class="hljs-string">"storage_capacity"</span>,
                <span class="hljs-string">"throughput_capacity"</span>,
                <span class="hljs-string">"automatic_backup_retention_days"</span>,
                <span class="hljs-string">"copy_tags_to_backups"</span>,
                <span class="hljs-string">"weekly_maintenance_start_time"</span>,
                <span class="hljs-string">"environment"</span>,
                <span class="hljs-string">"base_tags"</span>,
                <span class="hljs-string">"cor_selected_vpc"</span>,
                <span class="hljs-string">"cor_subnets_2"</span>,
                <span class="hljs-string">"cor_subnets_1"</span>,
                <span class="hljs-string">"cority_ems_sg"</span>,
            ]

            <span class="hljs-comment"># Combine args and kwargs into a single dictionary</span>
            all_args = {**dict(zip(arg_names, args)), **kwargs}

            <span class="hljs-comment"># Check types of specific arguments</span>
            bool_vars = [<span class="hljs-string">"create_active_directory"</span>, <span class="hljs-string">"copy_tags_to_backups"</span>]
            <span class="hljs-keyword">for</span> boolvar <span class="hljs-keyword">in</span> bool_vars:
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> isinstance(all_args.get(boolvar), bool):
                    <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">f"<span class="hljs-subst">{boolvar}</span> must be a boolean"</span>)

            int_vars = [
                <span class="hljs-string">"storage_capacity"</span>,
                <span class="hljs-string">"throughput_capacity"</span>,
                <span class="hljs-string">"automatic_backup_retention_days"</span>,
            ]
            <span class="hljs-keyword">for</span> var <span class="hljs-keyword">in</span> int_vars:
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> isinstance(all_args.get(var), int):
                    <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">f"<span class="hljs-subst">{var}</span> must be an integer"</span>)

            str_vars = [
                <span class="hljs-string">"service_name"</span>,
                <span class="hljs-string">"domain_name_suffix"</span>,
                <span class="hljs-string">"administrator_password"</span>,
                <span class="hljs-string">"directory_service_id"</span>,
                <span class="hljs-string">"deployment_type"</span>,
                <span class="hljs-string">"storage_type"</span>,
                <span class="hljs-string">"weekly_maintenance_start_time"</span>,
                <span class="hljs-string">"environment"</span>,
                <span class="hljs-string">"cor_selected_vpc"</span>,
                <span class="hljs-string">"cor_subnets_2"</span>,
                <span class="hljs-string">"cor_subnets_1"</span>,
                <span class="hljs-string">"cority_ems_sg"</span>,
            ]
            <span class="hljs-keyword">for</span> strvar <span class="hljs-keyword">in</span> str_vars:
                <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> isinstance(all_args.get(strvar), str):
                    <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">f"<span class="hljs-subst">{strvar}</span> must be a string"</span>)

            <span class="hljs-comment"># Call the original function</span>
            <span class="hljs-keyword">return</span> func(self, *args, **kwargs)

        <span class="hljs-keyword">return</span> wrapper

<span class="hljs-meta">    @type_check_decorator</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>):</span>
        <span class="hljs-comment"># Initialize attributes</span>
        self.create_active_directory = kwargs.get(<span class="hljs-string">"create_active_directory"</span>)
        self.service_name = kwargs.get(<span class="hljs-string">"service_name"</span>)
        self.domain_name_suffix = kwargs.get(<span class="hljs-string">"domain_name_suffix"</span>)
        self.administrator_password = kwargs.get(<span class="hljs-string">"administrator_password"</span>)
        self.directory_service_id = kwargs.get(<span class="hljs-string">"directory_service_id"</span>)
        self.deployment_type = kwargs.get(<span class="hljs-string">"deployment_type"</span>)
        self.storage_type = kwargs.get(<span class="hljs-string">"storage_type"</span>)
        self.storage_capacity = kwargs.get(<span class="hljs-string">"storage_capacity"</span>)
        self.throughput_capacity = kwargs.get(<span class="hljs-string">"throughput_capacity"</span>)
        self.automatic_backup_retention_days = kwargs.get(
            <span class="hljs-string">"automatic_backup_retention_days"</span>
        )
        self.copy_tags_to_backups = kwargs.get(<span class="hljs-string">"copy_tags_to_backups"</span>)
        self.weekly_maintenance_start_time = kwargs.get(<span class="hljs-string">"weekly_maintenance_start_time"</span>)
        self.environment = kwargs.get(<span class="hljs-string">"environment"</span>)
        self.base_tags = kwargs.get(<span class="hljs-string">"base_tags"</span>)
        self.cor_selected_vpc = kwargs.get(<span class="hljs-string">"cor_selected_vpc"</span>)
        self.cor_subnets_2 = kwargs.get(<span class="hljs-string">"cor_subnets_2"</span>)
        self.cor_subnets_1 = kwargs.get(<span class="hljs-string">"cor_subnets_1"</span>)
        self.cority_ems_sg = kwargs.get(<span class="hljs-string">"cority_ems_sg"</span>)
</code></pre>
<h3 id="heading-benefits-of-input-validation">Benefits of Input Validation</h3>
<h3 id="heading-readable-error-messages">Readable Error Messages</h3>
<p>Without input validation, errors can manifest in verbose stack traces that are often hard to decipher. Consider this typical Python error:</p>
<pre><code class="lang-plaintext">
Traceback (most recent call last):
  File "C:\Program Files (x86)\Pulumi\pulumi-language-python-exec", line 191, in &lt;module&gt;
    loop.run_until_complete(coro)
  File "C:\Python312\Lib\asyncio\base_events.py", line 687, in run_until_complete
    return future.result()
  File "C:\DevOps\awsinfrastructure.pulumi\iac-development\jcontent-dev\venv\Lib\site-packages\pulumi\runtime\stack.py", line 142, in run_in_stack
    await run_pulumi_func(run)
  File "C:\DevOps\awsinfrastructure.pulumi\iac-development\jcontent-dev\venv\Lib\site-packages\pulumi\runtime\stack.py", line 56, in run_pulumi_func
    await wait_for_rpcs()
  File "C:\DevOps\awsinfrastructure.pulumi\iac-development\jcontent-dev\venv\Lib\site-packages\pulumi\runtime\stack.py", line 89, in wait_for_rpcs
    raise exn from cause
  File "C:\DevOps\awsinfrastructure.pulumi\iac-development\jcontent-dev\venv\Lib\site-packages\pulumi\runtime\stack.py", line 81, in wait_for_rpcs
    await rpc_manager.rpcs.pop()
AssertionError: Unexpected type. Expected 'list' got '&lt;class 'str'&gt;'
</code></pre>
<p>In contrast, input validation provides clearer, more concise error messages:</p>
<pre><code class="lang-plaintext">Diagnostics:
  pulumi:pulumi:Stack (jcontent-dev-v2-jcontent_dev_v2):
    error: Program failed with an unhandled exception:
    Traceback (most recent call last):
      File "C:\DevOps\infrastructure.pulumi\iac-development\jcontent-dev-v2\__main__.py", line 60, in &lt;module&gt;
        fsxArgs = fsx.CorityFSxArgs(
                  ^^^^^^^^^^^^^^^^^^
      File "C:\DevOps\infrastructure.pulumi\iac-development\jcontent-dev-v2\../../pkg\cority_aws_fsx.py", line 144, in wrapper
        raise TypeError(f"{boolvar} must be a boolean")
    TypeError: copy_tags_to_backups must be a boolean
</code></pre>
<p>The bottom line of the error message is more concise and tells you where the problem is.</p>
<pre><code class="lang-plaintext">TypeError: copy_tags_to_backups must be a boolean
</code></pre>
<p>These specific messages reduce debugging time and improve developer productivity by directly pointing to the source of the error.</p>
<h3 id="heading-improved-debugging">Improved Debugging</h3>
<p>With input validation, errors are caught and reported closer to their source, making tracing and fixing issues easier. This clarity accelerates the development process and ensures smoother project execution.</p>
<h3 id="heading-cross-package-consistency">Cross-Package Consistency</h3>
<p>Input validation ensures that data flows consistently between different modules and packages. This consistency is crucial when using functions like <code>pulumi.export</code> to pass outputs between resources, allowing for controlled and predictable data handling. For instance, when handling Pulumi export values from one class to another, input validation ensures that the data types remain consistent, preventing integration issues and facilitating smoother transitions between components.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Input validation is a foundational practice that enhances the robustness and reliability of AWS resource management in Python. By adopting these techniques, developers can leverage Python's flexibility while mitigating the risks associated with dynamic typing. As cloud infrastructures become increasingly complex, the importance of such practices cannot be overstated. Implementing robust input validation improves code quality and fosters a culture of precision and accountability in software development, ultimately leading to more efficient and error-free deployment pipelines.</p>
<p>Happy Coding,</p>
<p><strong>The Cloud Dude</strong></p>
]]></content:encoded></item><item><title><![CDATA[Two ALB Target Groups One ECS with Pulumi & Python]]></title><description><![CDATA[Introduction
When designing a scalable and flexible architecture on AWS, it's common to use an Application Load Balancer (ALB) to distribute traffic across multiple targets, such as EC2 instances and ECS tasks. However, a challenge arises when you mu...]]></description><link>https://theclouddude.co.uk/two-alb-target-groups-one-ecs-with-pulumi-python</link><guid isPermaLink="true">https://theclouddude.co.uk/two-alb-target-groups-one-ecs-with-pulumi-python</guid><category><![CDATA[Python]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Pulumi]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Infrastructure as code]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 01 Aug 2024 12:00:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722265726787/19cb1d4f-9ac7-4b61-acfc-b002406c1fe4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>When designing a scalable and flexible architecture on AWS, it's common to use an Application Load Balancer (ALB) to distribute traffic across multiple targets, such as EC2 instances and ECS tasks. However, a challenge arises when you must support both EC2 instances and ECS Fargate tasks with the same ALB, especially when the target types differ. This article explains why and how multiple target groups for an ALB can be used to support both EC2 instances and ECS Fargate tasks.</p>
<h2 id="heading-problem-statement">Problem Statement</h2>
<p>In a recent work project, I encountered an issue where I needed to attach both EC2 instances and ECS Fargate tasks to an ALB. The ALB target group for ECS Fargate tasks requires <code>target_type="ip"</code>, while the target group for EC2 instances requires <code>target_type="instance"</code>. Attempting to use a single target group for both resulted in compatibility issues. When coding with Pulumi, AWS interprets the instance ID as an IPv6 address using <code>target_type="ip"</code>, resulting in a failed creation attempt.</p>
<h2 id="heading-solution-multiple-target-groups">Solution: Multiple Target Groups</h2>
<p>To resolve this issue, we decided to create two separate target groups:</p>
<ol>
<li><p><strong>Target Group for EC2 Instances</strong>: Uses <code>target_type="instance"</code>.</p>
</li>
<li><p><strong>Target Group for ECS Fargate Tasks</strong>: Uses <code>target_type="ip"</code>.</p>
</li>
</ol>
<p>This approach allows us to leverage the strengths of both EC2 instances and ECS Fargate tasks while ensuring compatibility with the ALB.</p>
<h2 id="heading-implementation">Implementation</h2>
<p>Here’s a step-by-step guide on implementing this solution using Pulumi in Python.</p>
<h3 id="heading-step-1-define-the-alb-with-two-target-groups">Step 1: Define the ALB with Two Target Groups</h3>
<p>First, we must create an ALB with two target groups: one for EC2 instances and one for ECS Fargate tasks.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">from</span> pulumi <span class="hljs-keyword">import</span> ResourceOptions, Output
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Mapping, List

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ALBArgs</span>:</span>
    <span class="hljs-string">"""The arguments necessary to construct an `ALB` resource"""</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">
        self,
        base_tags: Mapping[str, str],
        internal: bool,
        subnets: List[str],
        security_groups: List[str],
        enable_deletion_protection: bool,
        vpc_id: str,
        webserver_id: str,
        alb_listener: str,
    </span>):</span>
        self.internal = internal
        self.subnets = subnets
        self.security_groups = security_groups
        self.enable_deletion_protection = enable_deletion_protection
        self.vpc_id = vpc_id
        self.webserver_id = webserver_id
        self.alb_listener = alb_listener
        self.base_tags = base_tags

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JCProjectALB</span>(<span class="hljs-params">pulumi.ComponentResource</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, resource_name: str, args: ALBArgs, opts: ResourceOptions = None</span>):</span>
        super().__init__(<span class="hljs-string">"cority:aws:alb"</span>, resource_name, {}, opts)

        self.alb_name = resource_name
        self.internal = args.internal
        self.subnets = args.subnets
        self.security_groups = args.security_groups
        self.enable_deletion_protection = args.enable_deletion_protection
        self.vpc_id = args.vpc_id
        self.webserver_id = args.webserver_id
        self.alb_listener = args.alb_listener
        self.base_tags = args.base_tags

        self.alb = aws.lb.LoadBalancer(
            self.alb_name,
            internal=self.internal,
            load_balancer_type=<span class="hljs-string">"application"</span>,
            security_groups=self.security_groups,
            subnets=self.subnets,
            enable_deletion_protection=self.enable_deletion_protection,
            tags=self.base_tags,
            opts=ResourceOptions(parent=self),
        )

        <span class="hljs-comment"># Target group for EC2 instances</span>
        self.alb_target_group_instance = aws.lb.TargetGroup(
            <span class="hljs-string">f"<span class="hljs-subst">{self.alb_name[:<span class="hljs-number">20</span>]}</span>-tg-instance"</span>,
            port=<span class="hljs-number">80</span>,
            protocol=<span class="hljs-string">"HTTP"</span>,
            target_type=<span class="hljs-string">"instance"</span>,
            vpc_id=self.vpc_id,
            opts=ResourceOptions(parent=self.alb),
        )

        <span class="hljs-comment"># Target group for Fargate tasks</span>
        self.alb_target_group_ip = aws.lb.TargetGroup(
            <span class="hljs-string">f"<span class="hljs-subst">{self.alb_name[:<span class="hljs-number">20</span>]}</span>-tg-ip"</span>,
            port=<span class="hljs-number">80</span>,
            protocol=<span class="hljs-string">"HTTP"</span>,
            target_type=<span class="hljs-string">"ip"</span>,
            vpc_id=self.vpc_id,
            opts=ResourceOptions(parent=self.alb),
        )

        self.alb_target_group_attachment = aws.lb.TargetGroupAttachment(
            <span class="hljs-string">f"<span class="hljs-subst">{self.alb_name[:<span class="hljs-number">10</span>]}</span>-tg-attachment"</span>,
            target_id=self.webserver_id,
            target_group_arn=self.alb_target_group_instance.arn,
            port=<span class="hljs-number">80</span>,
            opts=ResourceOptions(parent=self.alb_target_group_instance),
        )

        self.listener = aws.lb.Listener(
            <span class="hljs-string">f"<span class="hljs-subst">{self.alb_name}</span>-listener"</span>,
            load_balancer_arn=self.alb.arn,
            port=<span class="hljs-number">80</span>,
            default_actions=[
                aws.lb.ListenerDefaultActionArgs(
                    type=<span class="hljs-string">"forward"</span>,
                    target_group_arn=self.alb_target_group_ip.arn,
                )
            ],
            opts=ResourceOptions(parent=self.alb),
        )

        pulumi.export(<span class="hljs-string">"alb_name"</span>, self.alb_name)
        pulumi.export(<span class="hljs-string">"listener_arn"</span>, self.listener.arn)
        pulumi.export(<span class="hljs-string">"targetgroup_instance_arn"</span>, self.alb_target_group_instance.arn)
        pulumi.export(<span class="hljs-string">"targetgroup_ip_arn"</span>, self.alb_target_group_ip.arn)

        self.register_outputs(
            {
                <span class="hljs-string">"alb_name"</span>: self.alb_name,
                <span class="hljs-string">"listener_arn"</span>: self.listener.arn,
                <span class="hljs-string">"targetgroup_instance_arn"</span>: self.alb_target_group_instance.arn,
                <span class="hljs-string">"targetgroup_ip_arn"</span>: self.alb_target_group_ip.arn,
            }
        )
</code></pre>
<h3 id="heading-step-2-define-the-ecs-service-to-use-the-ip-target-group">Step 2: Define the ECS Service to Use the IP Target Group</h3>
<p>Next, we must configure the ECS service to use the target group with <code>target_type="ip"</code>.</p>
<pre><code class="lang-python">
<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">from</span> pulumi <span class="hljs-keyword">import</span> ResourceOptions, Output
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Mapping, List
<span class="hljs-keyword">import</span> applicationloadbalancer
<span class="hljs-keyword">import</span> compute


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ECSArgs</span>:</span>
    <span class="hljs-string">"""The arguments necessary to construct an `ECS` resource"""</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">
        self,
        base_tags: Mapping[str, str],
        internal: bool,
        subnets: List[str],
        security_groups: List[str],
        enable_deletion_protection: bool,
        vpc_id: str,
        container_name: str,
        alb_listener: aws.lb.Listener,
        alb_target_group_arn: str,
    </span>):</span>
        self.internal = internal
        self.subnets = subnets
        self.security_groups = security_groups
        self.enable_deletion_protection = enable_deletion_protection
        self.vpc_id = vpc_id
        self.base_tags = base_tags
        self.alb_target_group_arn = alb_target_group_arn
        self.alb_listener = alb_listener
        self.container_name = container_name


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JCProjectECS</span>(<span class="hljs-params">pulumi.ComponentResource</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, resource_name: str, args: ECSArgs, opts: ResourceOptions = None</span>):</span>
        super().__init__(<span class="hljs-string">"cority:aws:ecs"</span>, resource_name, {}, opts)

        cluster_name = resource_name
        self.internal = args.internal
        self.subnets = args.subnets
        self.security_groups = args.security_groups
        self.enable_deletion_protection = args.enable_deletion_protection
        self.vpc_id = args.vpc_id
        self.base_tags = args.base_tags
        self.alb_target_group_arn = args.alb_target_group_arn
        self.alb_listener = args.alb_listener
        self.container_name = args.container_name

        <span class="hljs-comment"># Create an ECS cluster</span>
        self.cluster = aws.ecs.Cluster(cluster_name)

        <span class="hljs-comment"># Create an IAM role for task execution</span>
        task_execution_role = aws.iam.Role(
            <span class="hljs-string">"taskExecutionRole"</span>,
            assume_role_policy=<span class="hljs-string">"""{
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "ecs-tasks.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            }"""</span>,
        )

        <span class="hljs-comment"># Attach the required policies to the task execution role</span>
        task_execution_role_policy = aws.iam.RolePolicyAttachment(
            <span class="hljs-string">"taskExecutionRolePolicy"</span>,
            role=task_execution_role.name,
            policy_arn=<span class="hljs-string">"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"</span>,
        )

        <span class="hljs-comment"># Create a Fargate Task Definition</span>
        self.task_definition = aws.ecs.TaskDefinition(
            <span class="hljs-string">"task_definition"</span>,
            family=<span class="hljs-string">"my-fargate-task"</span>,
            cpu=<span class="hljs-string">"256"</span>,
            memory=<span class="hljs-string">"512"</span>,
            network_mode=<span class="hljs-string">"awsvpc"</span>,
            requires_compatibilities=[<span class="hljs-string">"FARGATE"</span>],
            execution_role_arn=task_execution_role.arn,
            container_definitions=Output.all().apply(
                <span class="hljs-keyword">lambda</span> _: <span class="hljs-string">"""[
                    {
                        "name": "investorportalcont",
                        "image": "nginx",
                        "cpu": 256,
                        "memory": 512,
                        "essential": true,
                        "portMappings": [
                            {
                                "containerPort": 80,
                                "hostPort": 80,
                                "protocol": "tcp"
                            }
                        ]
                    }
                ]"""</span>
            ),
        )
     <span class="hljs-comment"># ECS Service connected to the existing ALB</span>
        self.service = aws.ecs.Service(
            <span class="hljs-string">"ecs_service"</span>,
            cluster=self.cluster.arn,
            task_definition=self.task_definition.arn,
            desired_count=<span class="hljs-number">1</span>,
            launch_type=<span class="hljs-string">"FARGATE"</span>,
            network_configuration=aws.ecs.ServiceNetworkConfigurationArgs(
                subnets=args.subnets,
                security_groups=args.security_groups,
            ),
            load_balancers=[
                aws.ecs.ServiceLoadBalancerArgs(
                    target_group_arn=args.alb_target_group_arn,
                    container_name=args.container_name,
                    container_port=<span class="hljs-number">80</span>,
                ),
            ],
            opts=ResourceOptions(depends_on=[args.alb_listener]),
        )

        pulumi.export(<span class="hljs-string">"cluster_name"</span>, self.cluster.name)
        pulumi.export(<span class="hljs-string">"service_name"</span>, self.service.name)

        self.register_outputs(
            {
                <span class="hljs-string">"cluster_name"</span>: self.cluster.name,
                <span class="hljs-string">"service_name"</span>: self.service.name,
            }
        )
</code></pre>
<h3 id="heading-step-3-putting-everything-together-mainpyhttpmainpy">Step 3: Putting everything together <a target="_blank" href="http://main.py"><strong>main.py</strong></a></h3>
<p>We must now code <a target="_blank" href="http://main.py"><strong>main.py</strong></a> to pass through both alb target groups, one for the ECS and one for the EC2 instance; the actual connection for the EC2 instance happens within the ALB code as we point it to the `</p>
<pre><code class="lang-python"> target_group_arn=self.alb_target_group_ip.arn` line.
</code></pre>
<pre><code class="lang-python">
<span class="hljs-keyword">import</span> pulumi
<span class="hljs-keyword">import</span> pulumi_aws <span class="hljs-keyword">as</span> aws
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> sys

<span class="hljs-comment">#### This Section of Code will go and look for the PKG folder to then be able to import the modules</span>
<span class="hljs-comment">#### It also sets the PYTHONPATH which is needed to succesfully see the modules.</span>
module_path = <span class="hljs-string">"../pkg"</span>
<span class="hljs-keyword">if</span> module_path <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> sys.path:
    sys.path.append(module_path)

<span class="hljs-comment"># Try to import your custom module</span>
<span class="hljs-keyword">try</span>:
    <span class="hljs-keyword">import</span> compute  <span class="hljs-comment">## This is just a module that we are testing for.</span>

    pulumi.export(<span class="hljs-string">"module_import_status"</span>, <span class="hljs-string">"success"</span>)
<span class="hljs-keyword">except</span> ImportError <span class="hljs-keyword">as</span> e:
    pulumi.export(<span class="hljs-string">"module_import_status"</span>, <span class="hljs-string">"failed"</span>)
    pulumi.export(<span class="hljs-string">"import_error_message"</span>, str(e))
<span class="hljs-comment">#### END OF Import checking</span>

<span class="hljs-comment"># Import custom modules</span>
<span class="hljs-keyword">import</span> compute
<span class="hljs-keyword">import</span> applicationloadbalancer
<span class="hljs-keyword">import</span> networking
<span class="hljs-keyword">import</span> ecs

<span class="hljs-comment"># Define the VPC ID (Replace with your actual VPC ID)</span>
vpc_id = <span class="hljs-string">"vpc-0f0587443694534b4"</span>

<span class="hljs-comment"># Networking infrastructure</span>
cnetworking = networking.ExistingVPC(vpc_id)
vpcsubnet_id = cnetworking.subnet_id
vpcsecgroup_id = cnetworking.security_group_id

<span class="hljs-comment"># Convert Security Groups into Input of Strings</span>
security_group_strings = [sg <span class="hljs-keyword">for</span> sg <span class="hljs-keyword">in</span> vpcsecgroup_id]

<span class="hljs-comment"># Compute infrastructure</span>
ccompute = compute.CreateEC2Instances(
    <span class="hljs-string">"my-ec2-instance"</span>,
    compute.EC2Args(
        base_tags={<span class="hljs-string">"Project"</span>: <span class="hljs-string">"Investor Portal"</span>},
        internal=<span class="hljs-literal">False</span>,
        ami_instance_size=<span class="hljs-string">"t2.micro"</span>,
        subnet_id=vpcsubnet_id[<span class="hljs-number">0</span>],
        security_groups=security_group_strings,
    ),
)

<span class="hljs-comment"># Define ALB arguments and create ALB</span>
alb_args = applicationloadbalancer.ALBArgs(
    subnets=vpcsubnet_id,
    security_groups=vpcsecgroup_id,
    base_tags={<span class="hljs-string">"Project"</span>: <span class="hljs-string">"Investor Portal"</span>},
    internal=<span class="hljs-literal">False</span>,
    enable_deletion_protection=<span class="hljs-literal">False</span>,
    vpc_id=vpc_id,
    webserver_id=ccompute.server_instance.id,
    alb_listener=<span class="hljs-string">"corityalblistener"</span>,
)

build_alb = applicationloadbalancer.JCProjectALB(
    <span class="hljs-string">"investorpalb"</span>,
    alb_args,
    opts=pulumi.ResourceOptions(depends_on=[ccompute]),
)

<span class="hljs-comment"># Export ALB status</span>
pulumi.export(<span class="hljs-string">"alb_status"</span>, build_alb)

<span class="hljs-comment"># Define ECS arguments</span>
ecs_args = ecs.ECSArgs(
    base_tags={<span class="hljs-string">"Project"</span>: <span class="hljs-string">"Investor Portal"</span>},
    internal=<span class="hljs-literal">False</span>,
    subnets=vpcsubnet_id,
    security_groups=security_group_strings,
    enable_deletion_protection=<span class="hljs-literal">False</span>,
    vpc_id=vpc_id,
    container_name=<span class="hljs-string">"investorportalcont"</span>,
    alb_target_group_arn=build_alb.alb_target_group_ip.arn,
    alb_listener=build_alb.listener,
)

<span class="hljs-comment"># Create ECS</span>
ecs_cluster = ecs.JCProjectECS(
    <span class="hljs-string">"my-ecs"</span>, ecs_args, opts=pulumi.ResourceOptions(depends_on=[build_alb.listener])
)

<span class="hljs-comment"># Export ECS status</span>
pulumi.export(<span class="hljs-string">"ecs_cluster_name"</span>, ecs_cluster.cluster.name)
pulumi.export(<span class="hljs-string">"ecs_service_name"</span>, ecs_cluster.service.name)
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<hr />
<p>When working with cloud infrastructure and Pulumi, you sometimes have to think outside the box as different errors come flying at you. In this instance, I hope I have shown you how to get around the simple issue of putting two completely different resources onto the same ALB.</p>
<p>Happy coding,<br /><strong>Cloud Dude</strong></p>
]]></content:encoded></item><item><title><![CDATA[Writing a Bash CLI Program.]]></title><description><![CDATA[How Bash Saved the Day: Deploying Helm Charts with a Bash CLI Program
In the world of software development, encountering roadblocks is part of the journey. Recently, I faced a significant challenge while trying to deploy a Helm chart to a Kubernetes ...]]></description><link>https://theclouddude.co.uk/writing-a-bash-cli-program</link><guid isPermaLink="true">https://theclouddude.co.uk/writing-a-bash-cli-program</guid><category><![CDATA[Bash]]></category><category><![CDATA[bash script]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Helm]]></category><category><![CDATA[Go Language]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 18 Jul 2024 12:00:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720692056182/fe904050-ddc7-4e16-8bdb-0986e6578b7b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-how-bash-saved-the-day-deploying-helm-charts-with-a-bash-cli-program">How Bash Saved the Day: Deploying Helm Charts with a Bash CLI Program</h3>
<p>In the world of software development, encountering roadblocks is part of the journey. Recently, I faced a significant challenge while trying to deploy a Helm chart to a Kubernetes cluster using Go. Despite my best efforts and countless hours of troubleshooting, a critical bug in the <a target="_blank" href="http://github.com/mittwald/go-helm-client"><code>github.com/mittwald/go-helm-client</code></a> package thwarted my attempts. I wrote about that here: <a target="_blank" href="https://theclouddude.co.uk/the-painful-journey-of-deploying-a-helm-chart-to-kubernetes-with-go">Painful Experience with Go and Kubernetes.</a> However, Bash came to the rescue, allowing me to complete the deployment in a time-sensitive project at work. Here’s the story of how a simple Bash CLI Program saved the day.</p>
<h3 id="heading-the-challenge">The Challenge</h3>
<p>The task was to automate the deployment of several Helm charts to a Kubernetes cluster. Initially, I opted to use Go for this purpose, leveraging the <a target="_blank" href="http://github.com/mittwald/go-helm-client"><code>github.com/mittwald/go-helm-client</code></a> package. Unfortunately, a bug in the package prevented the successful deployment of the Helm charts, as detailed in my bug report <a target="_blank" href="https://github.com/mittwald/go-helm-client/issues/209">here</a>. With the project deadline looming, I needed a quick and reliable alternative.</p>
<h3 id="heading-the-solution-bash-cli-program">The Solution: Bash CLI Program</h3>
<p>Bash scripts are often underestimated, but their simplicity and power can be a lifesaver in critical situations. I wrote a Bash CLI program to handle the Helm deployments and integrated it into my Terraform script using the null function. Here’s the Bash script that made it all possible:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment">### Pass Flags</span>
<span class="hljs-comment">#Define the help function</span>
<span class="hljs-keyword">function</span> <span class="hljs-function"><span class="hljs-title">help</span></span>(){
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Options:"</span>;
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"-r    Resource Group Name"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"-l    Location of Resource Group"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"-c    Cluster Name"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"-e    Email for LetsEncrypt"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"-d    Domain name for Nginx"</span>
    <span class="hljs-built_in">exit</span> 1;
}

<span class="hljs-comment"># Initialize the default values for the variables.</span>
r=<span class="hljs-string">"rg"</span>;
l=<span class="hljs-string">"location"</span>;
c=<span class="hljs-string">"cluster"</span>;
e=<span class="hljs-string">"email"</span>;
d=<span class="hljs-string">"domainname"</span>;

<span class="hljs-comment"># Define the getopts variables</span>
options=<span class="hljs-string">":r:l:c:e:d:h"</span>;

<span class="hljs-comment"># Start the getopts code</span>
<span class="hljs-keyword">while</span> <span class="hljs-built_in">getopts</span> <span class="hljs-variable">${options}</span> opt; <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">case</span> <span class="hljs-variable">$opt</span> <span class="hljs-keyword">in</span>
            r) <span class="hljs-comment"># Get the resource group name</span>
                    rg=<span class="hljs-variable">${OPTARG}</span>
            ;;
            l) <span class="hljs-comment"># Get the location</span>
                  location=<span class="hljs-variable">${OPTARG}</span>
            ;;
            c) <span class="hljs-comment"># Get the cluster name</span>
                    cluster=<span class="hljs-variable">${OPTARG}</span>
            ;;
            e) <span class="hljs-comment"># Get the email</span>
                    email=<span class="hljs-variable">${OPTARG}</span>
            ;;
            d) <span class="hljs-comment"># Get the domain name</span>
                    domainname=<span class="hljs-variable">${OPTARG}</span>
            ;;
            h) <span class="hljs-comment"># Execute the help function</span>
                    <span class="hljs-built_in">help</span>;
            ;;
            \?) <span class="hljs-comment"># Unrecognized option - show help</span>
                    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Invalid option."</span>
                    <span class="hljs-built_in">help</span>;
            ;;
    <span class="hljs-keyword">esac</span>
<span class="hljs-keyword">done</span>

<span class="hljs-comment"># This tells getopts to move on to the next argument.</span>
<span class="hljs-built_in">shift</span> $((OPTIND-<span class="hljs-number">1</span>))
<span class="hljs-comment"># End getopts code</span>

<span class="hljs-comment">## Connect to Cluster</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Connecting to AKS Cluster"</span>
az aks get-credentials --resource-group <span class="hljs-string">"<span class="hljs-variable">$rg</span>"</span> --name <span class="hljs-string">"<span class="hljs-variable">$cluster</span>"</span> --overwrite-existing

<span class="hljs-comment">## Wait for the Cluster to Come online.</span>
sleep 2m
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Waiting For the Cluster to Fully Wake Up. This is so that Helm can install properly and not error. It's a 2-minute wait, go make a tea or coffee."</span>

<span class="hljs-comment">## Add the Repos</span>
helm repo add --force-update ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm repo add --force-update cert-manager https://charts.jetstack.io
helm repo update
helm repo add --force-update argo https://argoproj.github.io/argo-helm
helm repo update

<span class="hljs-comment">## Install nginx-ingress</span>
helm install nginx-ingress ingress-nginx/ingress-nginx --force \
--namespace ingress-nginx --create-namespace \
--<span class="hljs-built_in">set</span> controller.replicaCount=2 \
--<span class="hljs-built_in">set</span> controller.admissionWebhooks.patch.nodeSelector.<span class="hljs-string">"kubernetes\.io/os"</span>=linux \
--<span class="hljs-built_in">set</span> controller.service.annotations.<span class="hljs-string">"service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"</span>=/healthz \
--<span class="hljs-built_in">set</span> controller.service.externalTrafficPolicy=Local \
--<span class="hljs-built_in">set</span> controller.nodeSelector.<span class="hljs-string">"kubernetes\.io/os"</span>=linux \
--<span class="hljs-built_in">set</span> defaultBackend.nodeSelector.<span class="hljs-string">"kubernetes\.io/os"</span>=linux 

<span class="hljs-comment">## Install Cert Manager</span>
helm install cert-manager cert-manager/cert-manager --force \
--namespace cert-manager --create-namespace \
--version v1.14.5 \
--<span class="hljs-built_in">set</span> nodeSelector.<span class="hljs-string">"kubernetes\.io/os"</span>=linux \
--<span class="hljs-built_in">set</span> installCRDs=<span class="hljs-literal">true</span> \
--<span class="hljs-built_in">set</span> <span class="hljs-string">'extraArgs={--dns01-recursive-nameservers=1.1.1.1:53}'</span>

<span class="hljs-comment">## Install ArgoCD </span>
helm install argocd argo/argo-cd --force \
--namespace argocd --create-namespace \
--version <span class="hljs-string">"6.9.2"</span> \
--<span class="hljs-built_in">set</span> installCRDs=<span class="hljs-literal">true</span> \
-f argocd-values.yaml \
-f deployenvcongifmap.yaml

cat &lt;&lt; EOF | kubectl - 
port-forward svc/argocd-server 8080:443 
EOF

git <span class="hljs-built_in">clone</span> https://github.com/cloudflare/origin-ca-issuer.git

<span class="hljs-built_in">cd</span> origin-ca-issuer

kubectl apply -f deploy/crds

kubectl apply -f deploy/rbac

kubectl apply -f deploy/manifests

kubectl create secret generic jasons-api-key \
    -n cert-manager \
    --from-literal api-token=<span class="hljs-string">'API-TOKEN'</span>

sleep 60

cat  &lt;&lt; EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: lets-encrypt-jasons-cert
  namespace: cert-manager
spec:
  acme:
    email: <span class="hljs-string">"<span class="hljs-variable">$email</span>"</span>
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      <span class="hljs-comment"># Secret resource that will be used to store the account's private key.</span>
      name: lets-encrypt-jasons-cert
    solvers:
    - dns01:
        cloudflare:
          email: <span class="hljs-string">"<span class="hljs-variable">$email</span>"</span>
          apiTokenSecretRef:
            name: jasons-api-key
            key: api-token
      selector:
        dnsZones:
        - <span class="hljs-string">'companydomain.com'</span>
EOF

cat &lt;&lt; EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: letsencrypt-jasons-cert
spec:
  secretName: letsencrypt-jasons-cert
  issuerRef:
    name: lets-encrypt-jasons-cert
    kind: ClusterIssuer
  commonName: <span class="hljs-string">'testing-jc2.comapnydomain.com'</span>
  dnsNames:
  - <span class="hljs-string">'testing-jc2.comapnydomain.com'</span>
EOF

cat &lt;&lt; EOF | kubectl replace -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
  namespace: argocd
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 8080
    - name: https
      port: 443
      protocol: TCP
      targetPort: 8080
  selector:
    app.kubernetes.io/name: argocd-server
EOF

cat &lt;&lt; EOF | kubectl apply -f - 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server-ingress
  namespace: argocd
  annotations:
    cert-manager.io/cluster-issuer: lets-encrypt-jasons-cert
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: <span class="hljs-string">'true'</span>
    nginx.ingress.kubernetes.io/ssl-passthrough: <span class="hljs-string">'true'</span>
    nginx.ingress.kubernetes.io/backend-protocol: <span class="hljs-string">'HTTPS'</span>
spec:
  rules:
    - host: <span class="hljs-string">"<span class="hljs-variable">$domainname</span>"</span>
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: argocd-server
                port:
                  name: https
  tls:
    - hosts:
        - <span class="hljs-string">"<span class="hljs-variable">$domainname</span>"</span>
      secretName: argocdingress-cert
EOF

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Deployment Complete"</span>
</code></pre>
<h3 id="heading-integrating-with-terraform">Integrating with Terraform</h3>
<p>To automate the execution of this Bash CLI program within my Terraform script, I used the <code>null_resource</code> function. This allowed me to seamlessly call the Bash script and handle the Helm deployments as part of the Terraform workflow. Here’s a snippet of how I integrated the script with Terraform; I actually pass the variable values using a locals file:</p>
<pre><code class="lang-plaintext">resource "null_resource" "aks_configure" {
  provisioner "local-exec" {
    command = "./setupcluster.sh -r ${local.rg_name} -l ${local.location_for_bash_script} -c ${local.kubernetesclustername} -e ${local.letsencryptemail} -d ${local.fulldnsname}"
    interpreter = ["bash", "-c" ]
  }
  depends_on = [azurerm_kubernetes_cluster.new_prod_kubernetes, azurerm_resource_group.new_prod_rg]
}
</code></pre>
<h3 id="heading-the-benefits-of-bash">The Benefits of Bash</h3>
<ol>
<li><p><strong>Simplicity:</strong> Bash scripts are easy to write and understand. They don’t require complex setup or dependencies, making them ideal for quick automation tasks.</p>
</li>
<li><p><strong>Flexibility:</strong> With Bash, I could easily integrate various Helm commands and Kubernetes configurations and even clone Git repositories, it also uses the latest version of the CLI's.</p>
</li>
<li><p><strong>Reliability:</strong> The script provided a reliable way to deploy the Helm charts, overcoming the limitations I faced with Go.</p>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>While encountering challenges is inevitable in software development, finding alternative solutions is crucial. In this case, Bash proved to be a reliable and efficient tool, allowing me to complete the Helm deployments on time. This experience reinforced the importance of having multiple tools in your arsenal and being adaptable in the face of obstacles.</p>
<p>If you ever find yourself in a similar situation, don’t underestimate yourself and the knowledge you have.</p>
]]></content:encoded></item><item><title><![CDATA[From Unicorn Quests to Cloud Conquests: My Day at AWS's Exclusive GameDay Event]]></title><description><![CDATA[Last Tuesday was anything but ordinary. I was lucky enough to be invited to an exclusive GameDay event hosted by Amazon AWS at the iconic Reading Football Club. The excitement was palpable as I arrived at the venue bright and early at 09:30, ready fo...]]></description><link>https://theclouddude.co.uk/from-unicorn-quests-to-cloud-conquests-my-day-at-awss-exclusive-gameday-event</link><guid isPermaLink="true">https://theclouddude.co.uk/from-unicorn-quests-to-cloud-conquests-my-day-at-awss-exclusive-gameday-event</guid><category><![CDATA[AWS]]></category><category><![CDATA[iot]]></category><category><![CDATA[#unicorn]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[football]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 11 Jul 2024 23:00:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720693186202/4a4f1bb1-d8c3-4de9-9d97-9f3fb8785324.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last Tuesday was anything but ordinary. I was lucky enough to be invited to an exclusive GameDay event hosted by Amazon AWS at the iconic Reading Football Club. The excitement was palpable as I arrived at the venue bright and early at 09:30, ready for a day filled with innovation, competition, and, surprisingly, unicorns.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720693253728/665d8f4d-d8ce-49f0-b3cb-d7ef29a61bb3.jpeg" alt class="image--center mx-auto" /></p>
<p>As the day unfolded, it became clear that the focus was on the future—namely, IoT and cloud devices. AWS had set up a series of quests that required us to harness the full potential of their cloud services. But what truly caught me off guard was the whimsical theme woven into the technical challenges: unicorns.</p>
<p>We were divided into teams and tasked with completing various quests, all centred around these mythical creatures. It was a clever way to blend creativity with technology, making the learning process not just informative but thoroughly enjoyable. The competitive spirit was high, spurred on by an intimidating leaderboard that kept us all on our toes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720693331472/5488f59a-2175-4c88-a717-7c9ad7c3fd7b.jpeg" alt class="image--center mx-auto" /></p>
<p>Throughout the day, I had the pleasure of meeting many talented individuals, but one person who stood out was Bret. Our team quickly bonded over our shared goals and mutual enthusiasm for the tasks at hand. We navigated AWS Cloud's vast landscape, solving problems and unlocking new levels of the competition.</p>
<p>By the end of the event, our team proudly secured third place. While we didn't take home the grand prize, the experience was rewarding in itself. Plus, my laptop now sports two cool new stickers, a tangible reminder of an unforgettable day.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720694144891/4d1b7040-3a66-4c96-85fc-611994c8ebf9.jpeg" alt class="image--center mx-auto" /></p>
<p>This GameDay event was more than just a competition; it was a testament to the power of collaboration and the endless possibilities within the AWS ecosystem. Whether you're an IoT enthusiast, a cloud computing aficionado, or just someone who loves a good challenge, events like these are a fantastic way to expand your horizons and make new connections.</p>
<p>As I look at my newly adorned laptop, I'm reminded of the camaraderie, the learning, and, yes, the unicorns that made the day truly magical. Here's to more cloud conquests and the friendships forged along the way.</p>
<hr />
<p>Till Next Time<br /><strong>Happy Coding</strong><br />The Cloud Dude</p>
]]></content:encoded></item><item><title><![CDATA[Building My Own LLM: A Journey into Language Models Building a Tokenizer   🛠️]]></title><description><![CDATA[Welcome to Cloud Dude's Blog page! This blog post is based on the second video in our series on building Large Language Models (LLMs) from scratch, following the book "Build a Large Language Model from Scratch" by Sebastian Raschka, available at Mann...]]></description><link>https://theclouddude.co.uk/building-my-own-llm-a-journey-into-language-models-building-a-tokenizer</link><guid isPermaLink="true">https://theclouddude.co.uk/building-my-own-llm-a-journey-into-language-models-building-a-tokenizer</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[chatbot]]></category><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 11 Jul 2024 12:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720010447449/497a6b20-6317-4cd7-bca7-071a9c0bba17.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to <strong>Cloud Dude's</strong> Blog page! This blog post is based on the second video in our series on building Large Language Models (LLMs) from scratch, following the book <strong>"Build a Large Language Model from Scratch"</strong> by Sebastian Raschka, available at <a target="_blank" href="http://Manning.com">Manning.com</a>.</p>
<h3 id="heading-introduction">Introduction</h3>
<p>In this series, we're diving deep into the world of LLMs, like ChatGPT, to understand how they work and how to build one from scratch. This post covers Chapter 2 of the book, which focuses on building a dataset for training our LLM.</p>
<h3 id="heading-chapter-2-building-a-dataset">Chapter 2: Building a Dataset</h3>
<h4 id="heading-understanding-the-dataset">Understanding the Dataset</h4>
<p>The core idea behind an LLM is to take user input, process it, and generate meaningful output. For instance, if you ask the model to write a book about different breeds of cats, it needs to understand the request, fetch relevant information, and then generate the content. This process involves training the model on a dataset that acts as its knowledge base.</p>
<p>Below is an image on how this might look from a high level.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720010513656/daca1801-d58f-4974-8260-f2c893bc4348.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-training-the-dataset">Training the Dataset</h4>
<p>Training a dataset involves encoding a large amount of text into tokens that the model can understand and use. Essentially, you have to give it a vocabulary to then be able to go out into the world and understand and process the information. I mentioned in the YouTube video that I believe you should give it the English Dictionary because then it would have all the words and letters and would be able to process the information.</p>
<p>In the book, Sebastian Raschka uses "The Verdict" as an example dataset. The goal is to encode this book into tokens, which the model can then use to process other tasks.</p>
<h3 id="heading-practical-implementation">Practical Implementation</h3>
<h4 id="heading-using-jupyter-notebooks">Using Jupyter Notebooks</h4>
<p>The book provides code snippets in Jupyter Notebooks, which are great for running pieces of code interactively. However, as a developer, you might prefer to build a complete program. This involves creating classes and importing them into a main file to run the entire process as a cohesive unit.</p>
<p>Your folder structure would look like so:</p>
<p>┣ <a target="_blank" href="http://xn--main-4263c.py">📜main.py</a><br />┣ 📜simple_<a target="_blank" href="http://tokenizer.py">tokenizer.py</a></p>
<p>You would have the class in <code>simple_tokenizer.py</code> and that would look like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> re
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleTokenizerV1</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, vocab</span>):</span>
        self.str_to_int = vocab
        self.int_to_str = {i:s <span class="hljs-keyword">for</span> s,i <span class="hljs-keyword">in</span> vocab.items()}

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">encode</span>(<span class="hljs-params">self, text</span>):</span>
        preprocessed = re.split(<span class="hljs-string">r'([,.?_!"()\']|--|\s)'</span>, text)
        preprocessed = [
            item.strip() <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> preprocessed <span class="hljs-keyword">if</span> item.strip()
        ]
        <span class="hljs-comment">#ids = [self.str_to_int[s] for s in preprocessed]</span>
        ids = []
        <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> preprocessed:
            <span class="hljs-keyword">if</span> s <span class="hljs-keyword">in</span> self.str_to_int:
                ids.append(self.str_to_int[s])
            <span class="hljs-keyword">else</span>:
                print(<span class="hljs-string">f"Warning: '<span class="hljs-subst">{s}</span>' not found in vocabulary. Using '&lt;UNK&gt;' token."</span>)
                ids.append(self.str_to_int[self.unknown_token])
        <span class="hljs-keyword">return</span> ids

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decode</span>(<span class="hljs-params">self, ids</span>):</span>
        text = <span class="hljs-string">" "</span>.join([self.int_to_str[i] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> ids]) 

        text = re.sub(<span class="hljs-string">r'\s+([,.?!"()\'])'</span>, <span class="hljs-string">r'\1'</span>, text)
        <span class="hljs-keyword">return</span> text
</code></pre>
<p>Then, as we are working in Python, you would import that class into your <code>m̀ain.py</code> file. Like so</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> simple_tokenizer
</code></pre>
<p>To now use that class and process the vocabulary so that it can be put into tokens, you would write something similar to the following:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> urllib.request
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">import</span> simple_tokenizer

<span class="hljs-comment">## Download the Text File</span>
url = (<span class="hljs-string">"https://raw.githubusercontent.com/rasbt/"</span>
       <span class="hljs-string">"LLMs-from-scratch/main/ch02/01_main-chapter-code/"</span>
       <span class="hljs-string">"the-verdict.txt"</span>)
file_path = <span class="hljs-string">"the-verdict.txt"</span>
urllib.request.urlretrieve(url, file_path)

<span class="hljs-comment">## Read the Text File</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"the-verdict.txt"</span>, <span class="hljs-string">"r"</span>, encoding=<span class="hljs-string">"utf-8"</span>) <span class="hljs-keyword">as</span> f:
    raw_text = f.read()

<span class="hljs-comment"># Define The Vocab</span>
<span class="hljs-comment"># For the Vocab to work on a book you need to pass it a lot of vocabulary. Essentially to get this working I had to pass it the book</span>
<span class="hljs-comment"># to process the book. Is this not counter interuitive?</span>
<span class="hljs-comment"># Once it had all the vocab then it can run and process the book into tokens. </span>
<span class="hljs-comment">#vocab = {</span>
<span class="hljs-comment">#    'The': 1, 'verdict': 2, 'is': 3, 'in': 4, '.': 5,</span>
<span class="hljs-comment">#    'It': 6, 'was': 7, 'a': 8, 'great': 9, 'success': 10, 's':11</span>
<span class="hljs-comment">#}</span>

<span class="hljs-comment">## Now Process the Book and convert it to Tokens.</span>
preprocessed = re.split(<span class="hljs-string">r'([,.?_!"()\']|--|\s)'</span>, raw_text)
preprocessed = [item.strip() <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> preprocessed <span class="hljs-keyword">if</span> item.strip()]
print(len(preprocessed))

<span class="hljs-comment">## Sort words Alphabetically This I believe removes white space and vocab punctuation as well.</span>
all_words = sorted(set(preprocessed))
vocab_size = len(all_words)
print(vocab_size)
all_tokens = sorted(list(set(preprocessed)))
all_tokens.extend([<span class="hljs-string">"&lt;|endoftext|&gt;"</span>, <span class="hljs-string">"&lt;|unk|&gt;"</span>])

<span class="hljs-comment">## Now print the first 51 TokenIDs. </span>
vocab = {token:integer <span class="hljs-keyword">for</span> integer,token <span class="hljs-keyword">in</span> enumerate(all_words)}
vocab = {token:integer <span class="hljs-keyword">for</span> integer,token <span class="hljs-keyword">in</span> enumerate(all_tokens)}
<span class="hljs-keyword">for</span> i, item <span class="hljs-keyword">in</span> enumerate(vocab.items()):
    print(item)
    <span class="hljs-keyword">if</span> i &gt;= <span class="hljs-number">50</span>:
        <span class="hljs-keyword">break</span>

<span class="hljs-comment">## Initialise the tokenizer</span>
tokenizetext = simple_tokenizer.SimpleTokenizerV1(vocab)

<span class="hljs-comment">## Encode the Text</span>
converttoids = tokenizetext.encode(raw_text)
print(<span class="hljs-string">"Encoded Text:"</span>, converttoids)

<span class="hljs-comment">## Decode the Text</span>
decodeids = tokenizetext.decode(converttoids)
print(<span class="hljs-string">"Decoded Text"</span>, decodeids)
</code></pre>
<p>This would be simple enough to build a very basic vocabulary to process the book.</p>
<h4 id="heading-handling-errors">Handling Errors</h4>
<p>One common issue when working with datasets is handling unknown tokens. For example, if the model encounters a word that isn't in its vocabulary, it should handle this gracefully rather than throwing an error. In Python, this can be managed by checking for the presence of each token in the vocabulary and using a placeholder for unknown tokens.</p>
<p>You could do this like so:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleTokenizerBetterErrorV1</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, vocab</span>):</span>
        self.str_to_int = vocab
        self.int_to_str = {i: s <span class="hljs-keyword">for</span> s, i <span class="hljs-keyword">in</span> vocab.items()}
        self.unknown_token = <span class="hljs-string">'&lt;UNK&gt;'</span>
        self.str_to_int[self.unknown_token] = len(self.str_to_int) + <span class="hljs-number">1</span>
        self.int_to_str[len(self.int_to_str) + <span class="hljs-number">1</span>] = self.unknown_token

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">encode</span>(<span class="hljs-params">self, text</span>):</span>
        preprocessed = re.split(<span class="hljs-string">r'([,.?_!"()\']|--|\s)'</span>, text)
        preprocessed = [item.strip() <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> preprocessed <span class="hljs-keyword">if</span> item.strip()]
        ids = []
        <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> preprocessed:
            <span class="hljs-keyword">if</span> s <span class="hljs-keyword">in</span> self.str_to_int:
                ids.append(self.str_to_int[s])
            <span class="hljs-keyword">else</span>:
                print(<span class="hljs-string">f"Warning: '<span class="hljs-subst">{s}</span>' not found in vocabulary. Using '&lt;UNK&gt;' token."</span>)
                ids.append(self.str_to_int[self.unknown_token])
        <span class="hljs-keyword">return</span> ids

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decode</span>(<span class="hljs-params">self, ids</span>):</span>
        text = <span class="hljs-string">" "</span>.join([self.int_to_str[i] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> ids])
        text = re.sub(<span class="hljs-string">r'\s+([,.?!"()\'])'</span>, <span class="hljs-string">r'\1'</span>, text)
        <span class="hljs-keyword">return</span> text
</code></pre>
<p>Instead of Python just spitting out to me lines of code in my error that really do not make much sense:</p>
<pre><code class="lang-python">Traceback (most recent call last):
  File <span class="hljs-string">"/home/jason/Dev_Ops/LLMS/MyLLMVersion/Ch2/main.py"</span>, line <span class="hljs-number">52</span>, <span class="hljs-keyword">in</span> &lt;module&gt;
    converttoids = tokenizetext.encode(raw_text)
  File <span class="hljs-string">"/home/jason/Dev_Ops/LLMS/MyLLMVersion/Ch2/simple_tokenizer.py"</span>, line <span class="hljs-number">12</span>, <span class="hljs-keyword">in</span> encode
    ids = [self.str_to_int[s] <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> preprocessed]
  File <span class="hljs-string">"/home/jason/Dev_Ops/LLMS/MyLLMVersion/Ch2/simple_tokenizer.py"</span>, line <span class="hljs-number">12</span>, <span class="hljs-keyword">in</span> &lt;listcomp&gt;
    ids = [self.str_to_int[s] <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> preprocessed]
KeyError: <span class="hljs-string">'I'</span>
</code></pre>
<p>With the above code, I am able to make the error make better sense.</p>
<pre><code class="lang-python">Warning: <span class="hljs-string">'I'</span> <span class="hljs-keyword">not</span> found <span class="hljs-keyword">in</span> vocabulary. Using <span class="hljs-string">'&lt;UNK&gt;'</span> token.
Traceback (most recent call last):
  File <span class="hljs-string">"/home/jason/Dev_Ops/LLMS/MyLLMVersion/Ch2/main.py"</span>, line <span class="hljs-number">52</span>, <span class="hljs-keyword">in</span> &lt;module&gt;
    converttoids = tokenizetext.encode(raw_text)
  File <span class="hljs-string">"/home/jason/Dev_Ops/LLMS/MyLLMVersion/Ch2/simple_tokenizer.py"</span>, line <span class="hljs-number">19</span>, <span class="hljs-keyword">in</span> encode
    ids.append(self.str_to_int[self.unknown_token])
AttributeError: <span class="hljs-string">'SimpleTokenizerV1'</span> object has no attribute <span class="hljs-string">'unknown_token'</span>
</code></pre>
<p>As I touched on in the video, this really is a letdown of the Python language, and in Go, you can't do this. Go error handles well, and it also gets you, as a developer, to think about what type of error is going to come back to the user. Making you essentially a better developer.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<hr />
<p>I would highly recommend buying this book; you can get it from here: <a target="_blank" href="https://www.manning.com/books/build-a-large-language-model-from-scratch">https://www.manning.com/books/build-a-large-language-model-from-scratch</a></p>
<p>To begin building an LLM, you need to first build a tokenizer that can take in human language and convert it into numbers or, essentially, a database. Then, you can ask it to process data and return human-readable results.</p>
<p>I hope that was insightful, and I look forward to writing the next post about this project.</p>
<p>Happy coding,<br /><strong>Cloud Dude</strong></p>
]]></content:encoded></item><item><title><![CDATA[The Painful Journey of Deploying a Helm Chart to Kubernetes with Go]]></title><description><![CDATA[In the fast-paced world of software development, time-sensitive projects can often lead to both breakthroughs and challenges. Recently, I embarked on a project to deploy a Helm chart to a Kubernetes cluster using Go. This was because the helm Terrafo...]]></description><link>https://theclouddude.co.uk/the-painful-journey-of-deploying-a-helm-chart-to-kubernetes-with-go</link><guid isPermaLink="true">https://theclouddude.co.uk/the-painful-journey-of-deploying-a-helm-chart-to-kubernetes-with-go</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Helm]]></category><category><![CDATA[golang]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[development]]></category><category><![CDATA[Bugs and Errors]]></category><category><![CDATA[GitHub]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Wed, 10 Jul 2024 15:35:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718896948436/8f3ebc03-2034-479c-9703-0fe1509aa2b1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the fast-paced world of software development, time-sensitive projects can often lead to both breakthroughs and challenges. Recently, I embarked on a project to deploy a Helm chart to a Kubernetes cluster using Go. This was because the helm Terraform provider is currently not working as I would expect it to and does not allow you to build an Azure Kubernetes Cluster and Deploy a Helm chart in the same run.  </p>
<p>Despite my best efforts, a critical bug in a dependency and several deployment hurdles made the journey incredibly frustrating. Here's an account of my experience and the code I used, highlighting the pain points and lessons learned.</p>
<h4 id="heading-the-goal">The Goal</h4>
<p>The objective was simple: automate the deployment of a Helm chart to a Kubernetes cluster. This included setting up the Kubernetes Cert-Manager objects and leveraging the <a target="_blank" href="http://github.com/mittwald/go-helm-client"><code>github.com/mittwald/go-helm-client</code></a> package to manage Helm operations. Theoretically, it was straightforward. In practice, it proved to be anything but.</p>
<h3 id="heading-the-code">The Code</h3>
<p>Here is the code I used for this project. Note that sensitive information has been omitted for security reasons.</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"bytes"</span>
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"os"</span>
    <span class="hljs-string">"path/filepath"</span>
    <span class="hljs-string">"time"</span>

    <span class="hljs-string">"github.com/Azure/azure-sdk-for-go/sdk/azidentity"</span>
    <span class="hljs-string">"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v5"</span>
    certmanagerv1 <span class="hljs-string">"github.com/cert-manager/cert-manager"</span>
    helmclient <span class="hljs-string">"github.com/mittwald/go-helm-client"</span>
    <span class="hljs-string">"github.com/mittwald/go-helm-client/values"</span>
    <span class="hljs-string">"helm.sh/helm/v3/pkg/repo"</span>
    core <span class="hljs-string">"k8s.io/api/core/v1"</span>
    metav1 <span class="hljs-string">"k8s.io/apimachinery/pkg/apis/meta/v1"</span>
    <span class="hljs-string">"k8s.io/client-go/kubernetes"</span>
    _ <span class="hljs-string">"k8s.io/client-go/plugin/pkg/client/auth"</span>
    clientcmd <span class="hljs-string">"k8s.io/client-go/tools/clientcmd"</span>

    <span class="hljs-string">"github.com/Azure/azure-sdk-for-go/sdk/azidentity"</span>
    <span class="hljs-string">"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v5"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> outputBuffer bytes.Buffer
    ctx := context.Background()
    mycontext, _ := context.WithTimeout(ctx, <span class="hljs-number">80000</span>*time.Second)

    certmanageremail := <span class="hljs-string">"your-email@example.com"</span>
    subid := <span class="hljs-string">"your-subscription-id"</span>
    rg := <span class="hljs-string">"your-resource-group"</span>
    cluster := <span class="hljs-string">"your-cluster-name"</span>

    fmt.Println(<span class="hljs-string">"Connecting to Cluster Via Azure Call\n"</span>)
    myaksconnect := connectToAks(ctx, subid, rg, cluster)
    mypublicip := <span class="hljs-string">"0.0.0.0"</span>
    fmt.Printf(<span class="hljs-string">"Moved %v to KubeConfig File"</span>, &amp;myaksconnect.Name)
    fmt.Println(<span class="hljs-string">"Connecting to Cluster using KubeConfig File\n"</span>)
    kubeClient := connectToK8s()

    fmt.Println(<span class="hljs-string">"Building Helm Client\n"</span>)
    opt := &amp;helmclient.Options{
        Namespace:        <span class="hljs-string">"default"</span>,
        RepositoryCache:  <span class="hljs-string">"/tmp/.helmcache"</span>,
        RepositoryConfig: <span class="hljs-string">"/tmp/.helmrepo"</span>,
        Debug:            <span class="hljs-literal">false</span>,
        Linting:          <span class="hljs-literal">true</span>,
        DebugLog:         <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(format <span class="hljs-keyword">string</span>, v ...<span class="hljs-keyword">interface</span>{})</span></span> {},
        Output:           &amp;outputBuffer,
    }

    myHelmClient, err := helmclient.New(opt)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }

    fmt.Println(<span class="hljs-string">"Deploying nginx-ingress\n"</span>)
    nginxchartRepo := repo.Entry{
        Name: <span class="hljs-string">"nginx-ingress"</span>,
        URL:  <span class="hljs-string">"https://kubernetes.github.io/ingress-nginx"</span>,
    }

    <span class="hljs-keyword">if</span> err := myHelmClient.AddOrUpdateChartRepo(nginxchartRepo); err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    } <span class="hljs-keyword">else</span> {
        fmt.Printf(<span class="hljs-string">"Added Chart Repo %s\n"</span>, nginxchartRepo.Name)
    }

    <span class="hljs-keyword">if</span> err := myHelmClient.UpdateChartRepos(); err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    } <span class="hljs-keyword">else</span> {
        fmt.Printf(<span class="hljs-string">"Updating Chart Repo\n"</span>)
    }

    nginxchartSpec := helmclient.ChartSpec{
        ReleaseName:     <span class="hljs-string">"nginx-ingress/nginx-ingress"</span>,
        ChartName:       <span class="hljs-string">"nginx-ingress"</span>,
        Namespace:       <span class="hljs-string">"nginx-ingress"</span>,
        CreateNamespace: <span class="hljs-literal">true</span>,
        SkipCRDs:        <span class="hljs-literal">false</span>,
        Wait:            <span class="hljs-literal">true</span>,
        ValuesOptions: values.Options{
            StringValues: []<span class="hljs-keyword">string</span>{
                <span class="hljs-string">"rbac.create=false"</span>,
                <span class="hljs-string">"controller.service.externalTrafficPolicy=Local"</span>,
                fmt.Sprintf(<span class="hljs-string">"controller.service.loadBalancerIP=%v"</span>, mypublicip),
                <span class="hljs-string">"controller.replicaCount=2"</span>,
                <span class="hljs-string">"controller.nodeSelector.kubernetes\\.io/os=linux"</span>,
                <span class="hljs-string">"defaultBackend.nodeSelector.kubernetes\\.io/os=linux"</span>,
                <span class="hljs-string">"controller.admissionWebhooks.patch.nodeSelector.kubernetes\\.io/os=linux"</span>,
                <span class="hljs-string">"controller.publishService.enabled=true"</span>,
                <span class="hljs-string">"controller.service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path=/healthz"</span>,
            },
        },
    }
    nginxInstalledHelmChart, err := myHelmClient.InstallChart(mycontext, &amp;nginxchartSpec, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    }
    fmt.Printf(<span class="hljs-string">"Status of Chart Install %v,\n"</span>, *nginxInstalledHelmChart.Info)

    fmt.Println(<span class="hljs-string">"Deploying cert-manager\n"</span>)
    certmanagerchartRepo := repo.Entry{
        Name: <span class="hljs-string">"cert-manager"</span>,
        URL:  <span class="hljs-string">"https://charts.jetstack.io"</span>,
    }

    <span class="hljs-keyword">if</span> err := myHelmClient.AddOrUpdateChartRepo(certmanagerchartRepo); err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    } <span class="hljs-keyword">else</span> {
        fmt.Printf(<span class="hljs-string">"Added Chart Repo %s\n"</span>, certmanagerchartRepo.Name)
    }

    <span class="hljs-keyword">if</span> err := myHelmClient.UpdateChartRepos(); err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    } <span class="hljs-keyword">else</span> {
        fmt.Printf(<span class="hljs-string">"Updating Chart Repo\n"</span>)
    }

    certmanagerchartSpec := helmclient.ChartSpec{
        ReleaseName:     <span class="hljs-string">"cert-manager/cert-manager"</span>,
        ChartName:       <span class="hljs-string">"cert-manager"</span>,
        Version:         <span class="hljs-string">"v1.14.5"</span>,
        Namespace:       <span class="hljs-string">"cert-manager"</span>,
        CreateNamespace: <span class="hljs-literal">true</span>,
        ValuesOptions: values.Options{
            StringValues: []<span class="hljs-keyword">string</span>{
                <span class="hljs-string">"extraArgs = {--dns01-recursive-nameservers=1.1.1.1:53}"</span>,
                <span class="hljs-string">"controller.nodeSelector.kubernetes\\.io/os=linux"</span>,
            },
        },
        SkipCRDs: <span class="hljs-literal">false</span>,
        Wait:     <span class="hljs-literal">true</span>,
    }
    certmanagermyInstalledHelmChart, err := myHelmClient.InstallChart(mycontext, &amp;certmanagerchartSpec, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatal(err)
    }
    fmt.Printf(<span class="hljs-string">"Status of Chart Install %v,\n"</span>, *certmanagermyInstalledHelmChart.Info)

    <span class="hljs-comment">// Further deployments omitted for brevity...</span>

    fmt.Println(<span class="hljs-string">"Finished Deployment\n"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">connectToK8s</span><span class="hljs-params">()</span> *<span class="hljs-title">kubernetes</span>.<span class="hljs-title">Clientset</span></span> {
    home, exists := os.LookupEnv(<span class="hljs-string">"HOME"</span>)
    <span class="hljs-keyword">if</span> !exists {
        home = <span class="hljs-string">"C:\\Users\\your-user"</span>
    }

    configPath := filepath.Join(home, <span class="hljs-string">".kube"</span>, <span class="hljs-string">"config"</span>)

    config, err := clientcmd.BuildConfigFromFlags(<span class="hljs-string">""</span>, configPath)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Panicln(<span class="hljs-string">"Failed to create K8s config"</span>)
    }

    clientset, err := kubernetes.NewForConfig(config)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Panicln(<span class="hljs-string">"Failed to create K8s clientset"</span>)
    }

    <span class="hljs-keyword">return</span> clientset
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">connectToAks</span><span class="hljs-params">(ctx context.Context, subid <span class="hljs-keyword">string</span>, rg <span class="hljs-keyword">string</span>, aksname <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">armcontainerservice</span>.<span class="hljs-title">ManagedClustersClientGetAccessProfileResponse</span></span> {
    cred, err := azidentity.NewDefaultAzureCredential(<span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Failed to obtain a credential: %v"</span>, err)
    }

    clientFactory, err := armcontainerservice.NewClientFactory(subid, cred, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Failed to create client: %v"</span>, err)
    }
    res, err := clientFactory.NewManagedClustersClient().GetAccessProfile(ctx, rg, aksname, <span class="hljs-string">"clusterUser"</span>, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        log.Fatalf(<span class="hljs-string">"Failed to finish the request: %v"</span>, err)
    }
    <span class="hljs-keyword">return</span> &amp;res
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Ptr</span>[<span class="hljs-title">T</span> <span class="hljs-title">any</span>]<span class="hljs-params">(v T)</span> *<span class="hljs-title">T</span></span> {
    <span class="hljs-keyword">return</span> &amp;v
}
</code></pre>
<h3 id="heading-the-challenges">The Challenges</h3>
<ol>
<li><p><strong>Dependency Bugs:</strong> The biggest hurdle was a bug in the <a target="_blank" href="http://github.com/mittwald/go-helm-client"><code>github.com/mittwald/go-helm-client</code></a> package. Despite following the documentation and best practices, the Helm charts would not deploy correctly. I reported the issue here: <a target="_blank" href="https://github.com/mittwald/go-helm-client/issues/209">mittwald/go-helm-client#209</a>. This bug made deploying a working Helm chart impossible, derailing the project timeline.</p>
</li>
<li><p><strong>Time Sensitivity:</strong> This project was highly time-sensitive. Every minute spent troubleshooting the bug was a minute closer to the deadline, adding to the stress and frustration.</p>
</li>
<li><p><strong>Cert-Manager Objects:</strong> Deploying Kubernetes Cert-Manager objects using the native Kubernetes Go package was another pain point. Despite their theoretical simplicity, the objects did not deploy as expected, which was a significant setback, as Cert-Manager is critical for managing SSL/TLS certificates.</p>
</li>
</ol>
<h3 id="heading-lessons-learned">Lessons Learned</h3>
<ol>
<li><p><strong>Dependency Management:</strong> Always vet your dependencies thoroughly. Consider contingency plans for potential issues with third-party libraries in critical projects.</p>
</li>
<li><p><strong>Community and Support:</strong> Don't hesitate to reach out to the community or report issues.</p>
</li>
<li><p><strong>Time Management:</strong> Factor in potential delays when working with new or less-tested tools. Buffer time can help you better manage unexpected challenges.</p>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>While this project was fraught with challenges, it was a valuable learning experience. I learned a ton about the internals of Kubernetes and Helm, which has improved my understanding of how to build Apps for Kubernetes. I also hope it gives you an insight into how it could be done.  </p>
<p>Note: Please don't use this code, as it currently does not work. Once I have fixed the bug in the helm package, I will write another blog post about it.</p>
]]></content:encoded></item><item><title><![CDATA[Building My Own LLM: A Journey into Language Models  🛠️]]></title><description><![CDATA[Welcome to Cloud Dude's page! In this series, we will be building Large Language Models (LLMs) following the book "Build a Large Language Model from Scratch" by Sebastian Raschka, available at Manning.com.
Introduction
Large Language Models (LLMs), l...]]></description><link>https://theclouddude.co.uk/building-my-own-llm-a-journey-into-language-models</link><guid isPermaLink="true">https://theclouddude.co.uk/building-my-own-llm-a-journey-into-language-models</guid><category><![CDATA[LLM's ]]></category><category><![CDATA[Python]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[#languagemodelling]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 04 Jul 2024 12:00:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719672040203/91b2a302-45b9-410e-bdf4-88c5c8958c72.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to <strong>Cloud Dude's</strong> page! In this series, we will be building Large Language Models (LLMs) following the book <strong>"Build a Large Language Model from Scratch"</strong> by Sebastian Raschka, available at <a target="_blank" href="http://Manning.com">Manning.com</a>.</p>
<h3 id="heading-introduction">Introduction</h3>
<p>Large Language Models (LLMs), like ChatGPT, are powerful text generation and problem-solving tools. Building your own LLM ensures data privacy and control, allowing you to tailor the model to your specific needs and datasets. In this post, I will guide you through setting up the development environment required to follow along with the book.</p>
<h3 id="heading-setup">Setup</h3>
<h4 id="heading-1-clone-the-repository">1. Clone the Repository</h4>
<p>First, clone the repository from Rashbits GitHub to follow along with the book. This repository contains all the code and resources you will need.</p>
<h4 id="heading-2-install-miniforge">2. Install Miniforge</h4>
<p>Miniforge is essential for installing the dependencies needed for our LLM. Use the following commands to install Miniforge:</p>
<pre><code class="lang-sh">curl -L -O <span class="hljs-string">"https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-<span class="hljs-subst">$(uname)</span>-<span class="hljs-subst">$(uname -m)</span>.sh"</span>
bash Miniforge3-$(uname)-$(uname -m).sh
</code></pre>
<p>This command downloads and installs Miniforge, setting up the underpinnings for your LLM.</p>
<h4 id="heading-3-create-a-virtual-environment">3. Create a Virtual Environment</h4>
<p>Using Conda, create a virtual environment for Python. This is crucial for ensuring that Python runs correctly and that dependencies do not conflict with other projects on your machine.</p>
<pre><code class="lang-sh">conda create -n llm_env python=3.8
conda activate llm_env
</code></pre>
<h4 id="heading-4-install-required-libraries">4. Install Required Libraries</h4>
<p>To get the code from the book to work and to use the right libraries, you need to install JupyterLab and watermark:</p>
<pre><code class="lang-sh">conda install jupyterlab watermark
</code></pre>
<h4 id="heading-5-run-environment-check-script">5. Run Environment Check Script</h4>
<p>To ensure your environment matches the one used in the book, run Sebastian Raschka's setup script:</p>
<pre><code class="lang-sh">python /LLMs-from-scratch/setup/02_installing-python-libraries/python_environment_check.py
</code></pre>
<p>This script will tell you what is missing in your environment.</p>
<h4 id="heading-6-install-additional-dependencies">6. Install Additional Dependencies</h4>
<p>To fix any missing dependencies and align your environment with the book, run:</p>
<pre><code class="lang-sh">pip install -r /LLMs-from-scratch/requirements.txt --upgrade
</code></pre>
<p>This command installs all the required Python libraries listed in the <code>requirements.txt</code> file, ensuring that your environment is fully set up.</p>
<h3 id="heading-a-small-overview-of-the-book-so-far">A Small Overview Of The Book So Far</h3>
<ul>
<li><p><strong>Chapter 1</strong>: Introduces the theory behind LLMs and explains how they work.</p>
</li>
<li><p><strong>Chapter 2</strong>: Delves into the practical implementation of creating your own LLM. I am going to write a future Blog Post about this.</p>
</li>
</ul>
<p>The ultimate goal of the book and my blog posts is for you to have your own LLM running on your machine, trained on datasets of your choice. For example, you could train it on code to create a programming assistant.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Stay tuned for upcoming videos in this series, which aim to guide you through building and using your own LLM. Follow along with the book for a comprehensive learning experience. Happy coding!</p>
<hr />
<p>I would highly recommend buying this book; you can get it from here: <a target="_blank" href="https://www.manning.com/books/build-a-large-language-model-from-scratch">https://www.manning.com/books/build-a-large-language-model-from-scratch</a>  </p>
<p>And I look forward to you following along with me as we explore how to do this together over the next few weeks or months...  </p>
<p>As I said on my YouTube Channel (https://www.youtube.com/@TheCloudDude-24), I will eventually convert this LLM into a GoLang version just because I believe it will run better. I will also cover that as a new series after this one.</p>
<p>Happy coding,<br /><strong>Cloud Dude</strong></p>
]]></content:encoded></item><item><title><![CDATA[How I built an Authentication API in Go]]></title><description><![CDATA[In the world of web development, secure communication between client and server is paramount. This was the need of a client of mine, a startup called Greencloud; check them out here: https://www.greencloudcomputing.io/. This API is for a bigger proje...]]></description><link>https://theclouddude.co.uk/how-i-built-an-authentication-api-in-go</link><guid isPermaLink="true">https://theclouddude.co.uk/how-i-built-an-authentication-api-in-go</guid><category><![CDATA[golang]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[authentication]]></category><category><![CDATA[networking]]></category><category><![CDATA[Developer]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 20 Jun 2024 13:56:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718891680358/7d0e2f7f-1fa5-48ba-b301-c68e84bb8308.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of web development, secure communication between client and server is paramount. This was the need of a client of mine, a startup called Greencloud; check them out here: https://www.greencloudcomputing.io/. This API is for a bigger project that I am working on with them. I will hopefully get to blog about it one day soon.</p>
<p>One common way to secure these communications is through the use of authentication mechanisms. Today, I'll walk you through creating a custom Authentication API in Go, providing a flexible and reusable way to handle different authentication methods.</p>
<h3 id="heading-overview">Overview</h3>
<p>Our goal is to create a middleware-based HTTP client that can handle various types of authentication, such as username/password and API key authentication. We'll leverage Go's powerful type system and interfaces to achieve this.</p>
<h3 id="heading-the-code">The Code</h3>
<p>Here's the code we'll be working with:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"io"</span>
    <span class="hljs-string">"net/http"</span>
)

<span class="hljs-comment">// HTTPClient is an interface that can be used to perform HTTP requests</span>
<span class="hljs-keyword">type</span> HTTPClient <span class="hljs-keyword">interface</span> {
    Do(req *http.Request) (*http.Response, error)
}

<span class="hljs-comment">// HTTPClientFunc is a function that implements HTTPClient</span>
<span class="hljs-keyword">type</span> HTTPClientFunc <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(req *http.Request)</span> <span class="hljs-params">(*http.Response, error)</span></span>

<span class="hljs-comment">// Do implements HTTPClient</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(fn HTTPClientFunc)</span> <span class="hljs-title">Do</span><span class="hljs-params">(req *http.Request)</span> <span class="hljs-params">(*http.Response, error)</span></span> {
    <span class="hljs-keyword">return</span> fn(req)
}

<span class="hljs-comment">// Middleware is a function that can mutate the request before it is sent</span>
<span class="hljs-keyword">type</span> Middleware <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(HTTPClient)</span> <span class="hljs-title">HTTPClient</span></span>

<span class="hljs-comment">// UserPassAuthentication is middleware that adds username/password authentication</span>
<span class="hljs-comment">// to the request</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">UserPassAuthentication</span><span class="hljs-params">(username, password <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">Middleware</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(client HTTPClient)</span> <span class="hljs-title">HTTPClient</span></span> {
        <span class="hljs-keyword">return</span> HTTPClientFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(req *http.Request)</span> <span class="hljs-params">(*http.Response, error)</span></span> {
            req.Header.Set(<span class="hljs-string">"Authorization"</span>, <span class="hljs-string">"Bearer "</span>+username+password)
            <span class="hljs-keyword">return</span> client.Do(req)
        })
    }
}

<span class="hljs-comment">// APIKeyAuthentication is middleware that adds APIKey-based authentication to requests</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">APIKeyAuthentication</span><span class="hljs-params">(apikey <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">Middleware</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(client HTTPClient)</span> <span class="hljs-title">HTTPClient</span></span> {
        <span class="hljs-keyword">return</span> HTTPClientFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(req *http.Request)</span> <span class="hljs-params">(*http.Response, error)</span></span> {
            req.Header.Set(<span class="hljs-string">"Authorization"</span>, <span class="hljs-string">"Bearer "</span>+apikey)
            <span class="hljs-keyword">return</span> client.Do(req)
        })
    }
}

<span class="hljs-keyword">type</span> Client <span class="hljs-keyword">struct</span> {
    client HTTPClient
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GCNewClient</span><span class="hljs-params">(httpClient *http.Client, middleware ...Middleware)</span> <span class="hljs-title">Client</span></span> {
    client := Client{client: httpClient}

    <span class="hljs-comment">// Apply the middleware</span>
    <span class="hljs-keyword">for</span> _, m := <span class="hljs-keyword">range</span> middleware {
        client.client = m(client.client)
    }

    <span class="hljs-keyword">return</span> client
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Client)</span> <span class="hljs-title">Get</span><span class="hljs-params">(ctx context.Context, GCUrl <span class="hljs-keyword">string</span>)</span> <span class="hljs-params">([]<span class="hljs-keyword">byte</span>, error)</span></span> {
    req, err := http.NewRequestWithContext(ctx, <span class="hljs-string">"GET"</span>, GCUrl, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }

    resp, err := c.client.Do(req)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    <span class="hljs-keyword">defer</span> resp.Body.Close()

    <span class="hljs-comment">// Read the response body</span>
    body, err := io.ReadAll(resp.Body)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, fmt.Errorf(<span class="hljs-string">"failed to read http response body: %w"</span>, err)
    }

    fmt.Println(<span class="hljs-keyword">string</span>(body))
    <span class="hljs-keyword">return</span> body, err
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Replace with your API key</span>
    apiKey := <span class="hljs-string">"your-api-key-here"</span>

    <span class="hljs-comment">// Replace with the API endpoint</span>
    url := <span class="hljs-string">"https://your-api-endpoint.com"</span>

    client := GCNewClient(http.DefaultClient, APIKeyAuthentication(apiKey))
    client.Get(context.TODO(), url)
}
</code></pre>
<h3 id="heading-explanation">Explanation</h3>
<h4 id="heading-interfaces-and-middleware">Interfaces and Middleware</h4>
<p>We start by defining an <code>HTTPClient</code> interface and a function type <code>HTTPClientFunc</code> that implements this interface. This allows us to create flexible middleware that can modify requests before they are sent.</p>
<h4 id="heading-authentication-middleware">Authentication Middleware</h4>
<p>We then define two middleware functions:</p>
<ol>
<li><p><strong>UserPassAuthentication:</strong> Adds a username and password to the request header.</p>
</li>
<li><p><strong>APIKeyAuthentication:</strong> Adds an API key to the request header.</p>
</li>
</ol>
<p>These middleware functions return a new <code>HTTPClient</code> that modifies the request headers as needed.</p>
<h4 id="heading-client-struct-and-constructor">Client Struct and Constructor</h4>
<p>The <code>Client</code> Struct holds an <code>HTTPClient</code>, and the <code>GCNewClient</code> Function applies the provided middleware to this client.</p>
<h4 id="heading-get-request-method">GET Request Method</h4>
<p>The <code>Get</code> Method constructs an HTTP GET request and uses the client to execute it. It reads the response body and returns it.</p>
<h3 id="heading-putting-it-all-together">Putting It All Together</h3>
<p>In the <code>main</code> Function, we create a new client with API key authentication and use it to make a GET request to a specified endpoint. Remember to replace the placeholders with your actual API key and endpoint.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>This approach provides a clean and modular way to handle different authentication mechanisms in Go. By using middleware, we can easily add or modify authentication methods without changing the core logic of our HTTP client.</p>
<p>Feel free to use and adapt this code for your projects. Happy coding!</p>
<p>I would like to thank Adam Talbot from the Kubernetes Go Community for helping with this project. Without his help, I would still be stuck looking at the NET/HTTP Package. You can find him here: <a target="_blank" href="https://uk.linkedin.com/in/mrtalbot">https://uk.linkedin.com/in/mrtalbot</a></p>
<p>Please ensure you replace sensitive information, such as actual API keys, with placeholders before sharing your code publicly.</p>
]]></content:encoded></item><item><title><![CDATA[Working with Stacks, AI, Go and Pulumi]]></title><description><![CDATA[Recently, I have been tasked with checking out other IAC tools where I work. One of those given to me was Pulumi. At first, I was not sure. I come from a Terraform background and have been using Terraform for around six years now, so I was hesitant t...]]></description><link>https://theclouddude.co.uk/working-with-stacks-ai-go-and-pulumi</link><guid isPermaLink="true">https://theclouddude.co.uk/working-with-stacks-ai-go-and-pulumi</guid><category><![CDATA[AWS]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Pulumi]]></category><category><![CDATA[AI]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Fri, 16 Feb 2024 13:00:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708014150581/92dce770-281c-43a2-821f-4d00111fe1ce.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I have been tasked with checking out other IAC tools where I work. One of those given to me was Pulumi. At first, I was not sure. I come from a Terraform background and have been using Terraform for around six years now, so I was hesitant to pick this up at first.</p>
<p>At first, Pulumi did not make much sense to me. My initial thought was they were trying to copy Hashicorp. Yet the more I used it, the more it has grown on me.</p>
<p>I like it because if you are a Dev and not a DevOps person, I believe you will like Pulumi perhaps more than Terraform. The main reason is that you can bring your language of choice to the platform and start making cloud Infrastructure without going off and learning a whole new language.</p>
<p>After two weeks of messing with Pulumi and Go, let me show you what I did.</p>
<p>The most impressive part I have seen from Pulumi and Go is the Auto Package. <a target="_blank" href="https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v3/go/auto">Auto PKG Link</a> The Auto package brings a particular element of A. I to your program. It essentially lets you automate the whole process of running a Pulumi program.</p>
<p>If you want to run a Pulumi program, in the cmd line, you run Pulumi Up. In the picture below, you will see when Pulumi runs, it will ask if you want to create a Stack. A Stack is like a state file if you come from a Terraform background. You say yes and give your Stack a name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707993559769/eb7d6be7-deec-4f1a-a1e7-f6921b7ca1d1.png" alt class="image--center mx-auto" /></p>
<p>Now, that's all good if you're running this locally and don't mind interacting with Pulumi.  </p>
<p>But what about if you want to run this in a Pipeline? You can't have the pipeline prompt you to create a new stack. This is where the Auto Pkg starts to solve this for you. There may be other options for this. It would be worth checking out <code>pulumi --config</code> argument. <a target="_blank" href="https://www.pulumi.com/docs/cli/commands/pulumi_up/">Pulumi Up Docs</a></p>
<p>First, you must set your context, as many Pulumi Functions use the Context package. (I am not sure why this is; their documentation says it's necessary for Go to work, yet I've seen Go work fine without context. Yet that's what is required.)</p>
<p>So, you start with <code>context.Background</code>() then, you need to declare the following variables. Now, with the pipeline, You could put these in as ENV variables, but for the sake of this demo, we will hard-code these. (Later on, I will write more blog posts on this, and it will probably show a pipeline run in GitLab, so keep tuned in if you want to see that.) The Variables are: <code>stackName</code> , and the <code>projectName</code>. For obvious reasons, this <code>stackName</code> is what we will call the Stack. The <code>projectName</code> It is my understanding, and this could be wrong, but I believe the project is the top-level folder of what you will create; for example, this project will build an EC2 instance in AWS, so I called my Project ec2_deploy. If you run Pulumi manually, it will just go and grab the top-level folder name and call that the project.</p>
<p>Now, we are going to pass. <code>context.Background()</code>, <code>stackName</code>, and the <code>ProjectName</code> with a separate deploy func variable function called <code>deployFunc</code> (More on this later). To the function <code>UpsertStackInlineSource</code> What this function does is it takes all our variables and returns a created stack and an error to us. If the Stack is already created, the function won't error. (This may enable us to handle errors better than just running the <code>pulumi up</code> command and capturing a CLI error.) It will select the Stack and return the created Stack to us. This is handy if you run your pipeline over an already-created environment. So, the code will look a bit like this. (Side note: ensure you have <code>"</code><a target="_blank" href="http://github.com/pulumi/pulumi/sdk/v3/go/auto"><code>github.com/pulumi/pulumi/sdk/v3/go/auto</code></a><code>"</code> on your imports; otherwise, this won't work.)</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/pulumi/pulumi/sdk/v3/go/auto"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {

    ctx := context.Background()

    projectName := <span class="hljs-string">"ec2_deploy"</span>
    <span class="hljs-comment">// we use a simple stack name here, but recommend using auto.FullyQualifiedStackName for maximum specificity.</span>
    stackName := <span class="hljs-string">"dev"</span>
    <span class="hljs-comment">// stackName := auto.FullyQualifiedStackName("myOrgOrUser", projectName, stackName)</span>

    <span class="hljs-comment">// create or select a stack matching the specified name and project.</span>
    <span class="hljs-comment">// this will set up a workspace with everything necessary to run our inline program (deployFunc)</span>
    createdStack, err := auto.UpsertStackInlineSource(ctx, stackName, projectName, deployFunc)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to set up a workspace: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

}
</code></pre>
<p>As we are working with AWS, we need to create a workspace; my only explanation for this workspace idea is the Python equivalent of a Virtual Environment. I still don't fully understand this concept, but according to various examples, we need it. We also need to install the AWS tools to deploy our EC2 instance. The code will look like this.</p>
<pre><code class="lang-go"> workspace := createdStack.Workspace()

    fmt.Println(<span class="hljs-string">"Installing the AWS plugin"</span>)

    <span class="hljs-comment">// for inline source programs, we must manage plugins ourselves</span>
    err = workspace.InstallPlugin(ctx, <span class="hljs-string">"aws"</span>, <span class="hljs-string">"v6.22.0"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to install program plugins: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

    fmt.Println(<span class="hljs-string">"Successfully installed AWS plugin"</span>)
</code></pre>
<p>Depending on how you are signing into AWS from Pulumi, where I am, we are using SSO profiles. Again, this can be a manual process when running through the Pulumi command line, but when operating the Auto Pkg, you can set the config to use the AWS profile to sign in and deploy the EC2 instance.  </p>
<p>How we are going to do that is by using a function called, <code>SetAllConfig()</code> There is another function called. <code>SetConfig()</code> But that function will only let you set one item to the configuration. In our use case, we need to send over the AWS Region; for the project I'm working on, I need to send over a couple of custom values. It was also cool to send many values to see how this would work.  </p>
<p>These values, though, are weirdly not sent to the workspace but to the Stack we made at the beginning. When I was initially coding this, I presumed, with the linear process the program is running in, that we had moved from the Stack to the workspace and that all settings I was configuring were now in the workspace, not the Stack. Yet that's incorrect, all settings and configs are set to the workspace.</p>
<p>To send our config to the Stack, we are going to use the map called <code>ConfigMap</code> in the Auto PKG, which contains a field called <code>ConfigValue</code> you need to set the name fields of this map in a YAML format; for example, <code>aws:region</code> don't do <code>aws:region:</code> as this won't work. (Hours of troubleshooting and pain brought me to this conclusion). An example of what I am talking about is below. Side Note: You must also pass the context variable we set earlier to this function.  </p>
<pre><code class="lang-go">cfg := auto.ConfigMap{
        <span class="hljs-string">"aws:region"</span>:             auto.ConfigValue{Value: <span class="hljs-string">"us-east-1"</span>},
        <span class="hljs-string">"company:ticket"</span>:          auto.ConfigValue{Value: <span class="hljs-string">"CLOUD"</span>},
        <span class="hljs-string">"company:hubname"</span>: auto.ConfigValue{Value: <span class="hljs-string">"MYHUB"</span>},
    }

    createdStack.SetAllConfig(ctx, cfg)
</code></pre>
<p>Then, as we are sending it to the Stack, we also need to refresh our Stack to receive the new config. For that, we are going to use the <code>Refresh()</code> function again, you need to send your context variable to the function for this to work.  </p>
<p>I can hear the cogs turning. Why don't you just set the configuration to the Stack at the beginning with the initial function? That is a great question; simply, it doesn't work; the only way you can set the config to the Stack that I could see with the documentation was this way. Again, I could be wrong, as I have just started this process with Pulumi.</p>
<pre><code class="lang-go">    _, err = createdStack.Refresh(ctx)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to refresh stack: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }
</code></pre>
<p>The last thing we need to code for working with Stacks is the Pulumi-up part to make it all work. For that, we are going to use the <code>Up()</code> function. This works the same way as typing in <code>pulumi up -y</code> into your terminal.  </p>
<p>For this function to work, it uses the package <code>optup</code> function <code>ProgressStreams()</code> , which uses <code>io.writer</code> to write the output of what Pulumi is sending to a console. This should still work in a pipeline scenario, which I will see at a later date when I build one. Pkgs Documentation for more details: <a target="_blank" href="https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v3/go/auto/optup#ProgressStreams">Opt Upt PKG Link</a></p>
<p>Your code for this part should look like this</p>
<pre><code class="lang-go">    <span class="hljs-comment">// wire up our update to stream progress to stdout</span>
    stdoutStreamer := optup.ProgressStreams(os.Stdout)

    <span class="hljs-comment">// run the update to deploy our EC2 Cluster</span>
    _, err = createdStack.Up(ctx, stdoutStreamer)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to update stack: %v\n\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }
</code></pre>
<p>Your program code at this point should look like this:  </p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/pulumi/pulumi/sdk/v3/go/auto"</span>
    <span class="hljs-string">"github.com/pulumi/pulumi/sdk/v3/go/auto/optup"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {

    ctx := context.Background()

    projectName := <span class="hljs-string">"ec2_deploy"</span>
    <span class="hljs-comment">// we use a simple stack name here, but recommend using auto.FullyQualifiedStackName for maximum specificity.</span>
    stackName := <span class="hljs-string">"dev"</span>
    <span class="hljs-comment">// stackName := auto.FullyQualifiedStackName("myOrgOrUser", projectName, stackName)</span>

    <span class="hljs-comment">// create or select a stack matching the specified name and project.</span>
    <span class="hljs-comment">// this will set up a workspace with everything necessary to run our inline program (deployFunc)</span>
    createdStack, err := auto.UpsertStackInlineSource(ctx, stackName, projectName, deployFunc)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to set up a workspace: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

    workspace := createdStack.Workspace()

    fmt.Println(<span class="hljs-string">"Installing the AWS plugin"</span>)

    <span class="hljs-comment">// for inline source programs, we must manage plugins ourselves</span>
    err = workspace.InstallPlugin(ctx, <span class="hljs-string">"aws"</span>, <span class="hljs-string">"v6.22.0"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to install program plugins: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

    fmt.Println(<span class="hljs-string">"Successfully installed AWS plugin"</span>)

    cfg := auto.ConfigMap{
        <span class="hljs-string">"aws:region"</span>:             auto.ConfigValue{Value: <span class="hljs-string">"us-east-1"</span>},
        <span class="hljs-string">"company:ticket"</span>:          auto.ConfigValue{Value: <span class="hljs-string">"CLOUD"</span>},
        <span class="hljs-string">"company:hubname"</span>: auto.ConfigValue{Value: <span class="hljs-string">"MYHUB"</span>},
    }

    createdStack.SetAllConfig(ctx, cfg)

    _, err = createdStack.Refresh(ctx)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to refresh stack: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

    <span class="hljs-comment">// wire up our update to stream progress to stdout</span>
    stdoutStreamer := optup.ProgressStreams(os.Stdout)

    <span class="hljs-comment">// run the update to deploy our EC2 Cluster</span>
    _, err = createdStack.Up(ctx, stdoutStreamer)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to update stack: %v\n\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

}
</code></pre>
<p>Yet, if you were to run this program like in my picture below, it would fail...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1708008064411/627ecfca-9de3-4e71-afc7-dd7f79c1fb97.png" alt class="image--center mx-auto" /></p>
<p><strong>WHY?</strong>  </p>
<p>It's failing because our first function <code>UpsertStackInlineSource()</code> to piece all this together is missing the <code>deployFunc</code> variable we have not yet set to deploy our EC2 instance. Now, I must say I don't like this, the reason being that the way you code a function essentially is with a variable, not the usual GO way with <code>func(){}</code> . The <code>UpsertStackInlineSource()</code> it's not very GO Idiomatic. I couldn't figure out how to pass a function to this function. I will at a later point in time, but for the purpose of this blog post, we will keep it a variable with the note to change it later so that we can keep to Go's Idiomaticness. (If that's a word.) Again, as always, I could be wrong and missing something here, but I could not get this function to work with another function during my tired hours.  </p>
<pre><code class="lang-go"><span class="hljs-comment">// Deploy the EC2 Instance</span>
    deployFunc := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(ctx *pulumi.Context)</span> <span class="hljs-title">error</span></span> {
        ubuntu, err := ec2.LookupAmi(ctx, &amp;ec2.LookupAmiArgs{
            MostRecent: pulumi.BoolRef(<span class="hljs-literal">true</span>),
            Filters: []ec2.GetAmiFilter{
                {
                    Name: <span class="hljs-string">"name"</span>,
                    Values: []<span class="hljs-keyword">string</span>{
                        <span class="hljs-string">"ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"</span>,
                    },
                },
                {
                    Name: <span class="hljs-string">"virtualization-type"</span>,
                    Values: []<span class="hljs-keyword">string</span>{
                        <span class="hljs-string">"hvm"</span>,
                    },
                },
            },
            Owners: []<span class="hljs-keyword">string</span>{
                <span class="hljs-string">"099720109477"</span>,
            },
        }, <span class="hljs-literal">nil</span>)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> err
        }
        amiinstanceid := pulumi.Sprintf(ubuntu.Id)
        _, err = ec2.NewInstance(ctx, <span class="hljs-string">"web"</span>, &amp;ec2.InstanceArgs{
            Ami:          amiinstanceid,
            InstanceType: pulumi.String(<span class="hljs-string">"t3.micro"</span>),
            Tags: pulumi.StringMap{
                <span class="hljs-string">"Name"</span>: pulumi.String(<span class="hljs-string">"HelloWorld"</span>),
            },
        })
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> err
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }
</code></pre>
<p>The above code originated from the Pulumi website on how to deploy an EC2 instance. Yet there is a big error: It's on the line before <code>_, err = ec2.NewInstance(ctx, "web", &amp;ec2.InstanceArgs{</code> where you have to get the Instance ID of the AMI. There seems to be an issue with Pulumi and passing IDs around. The way I got around this was to use <code>pulumi.Sprintf</code> , which converted the ID into a string that was acceptable to the AMI field.  </p>
<p>Also, please make sure you have the following library in your import statement:<br /><code>"</code><a target="_blank" href="http://github.com/pulumi/pulumi/sdk/v3/go/pulumi"><code>github.com/pulumi/pulumi/sdk/v3/go/pulumi</code></a><code>"</code> and <code>"</code><a target="_blank" href="http://github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"><code>github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2</code></a><code>"</code> otherwise, this won't work.</p>
<p>I won't go into the EC2 code too much now, as this Blog post is long enough. I will be, however, writing another post about deploying the EC2 instance with Go and Pulumi at a later date :).</p>
<p>The code for this variable has to go up the top now in <code>main(){}</code>, before our <code>UpsertStackInlineSource</code>() function call because Go runs in a sequential order, so if you put the <code>deployFunc</code> after the <code>UperStackInlineSource()</code> call, it will error and say <code>deployFunc</code> does not exist. Your whole program code should now look like this:  </p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ec2"</span>
    <span class="hljs-string">"github.com/pulumi/pulumi/sdk/v3/go/auto"</span>
    <span class="hljs-string">"github.com/pulumi/pulumi/sdk/v3/go/auto/optup"</span>
    <span class="hljs-string">"github.com/pulumi/pulumi/sdk/v3/go/pulumi"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {

    <span class="hljs-comment">// Deploy the EC2 Instance</span>
    deployFunc := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(ctx *pulumi.Context)</span> <span class="hljs-title">error</span></span> {
        ubuntu, err := ec2.LookupAmi(ctx, &amp;ec2.LookupAmiArgs{
            MostRecent: pulumi.BoolRef(<span class="hljs-literal">true</span>),
            Filters: []ec2.GetAmiFilter{
                {
                    Name: <span class="hljs-string">"name"</span>,
                    Values: []<span class="hljs-keyword">string</span>{
                        <span class="hljs-string">"ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"</span>,
                    },
                },
                {
                    Name: <span class="hljs-string">"virtualization-type"</span>,
                    Values: []<span class="hljs-keyword">string</span>{
                        <span class="hljs-string">"hvm"</span>,
                    },
                },
            },
            Owners: []<span class="hljs-keyword">string</span>{
                <span class="hljs-string">"099720109477"</span>,
            },
        }, <span class="hljs-literal">nil</span>)
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> err
        }
        amiinstanceid := pulumi.Sprintf(ubuntu.Id)
        _, err = ec2.NewInstance(ctx, <span class="hljs-string">"web"</span>, &amp;ec2.InstanceArgs{
            Ami:          amiinstanceid,
            InstanceType: pulumi.String(<span class="hljs-string">"t3.micro"</span>),
            Tags: pulumi.StringMap{
                <span class="hljs-string">"Name"</span>: pulumi.String(<span class="hljs-string">"HelloWorld"</span>),
            },
        })
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> err
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
    }

    ctx := context.Background()

    projectName := <span class="hljs-string">"ec2_deploy"</span>
    <span class="hljs-comment">// we use a simple stack name here, but recommend using auto.FullyQualifiedStackName for maximum specificity.</span>
    stackName := <span class="hljs-string">"dev"</span>

    <span class="hljs-comment">// create or select a stack matching the specified name and project.</span>
    <span class="hljs-comment">// this will set up a workspace with everything necessary to run our inline program (deployFunc)</span>
    createdStack, err := auto.UpsertStackInlineSource(ctx, stackName, projectName, deployFunc)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to set up a workspace: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

    workspace := createdStack.Workspace()

    fmt.Println(<span class="hljs-string">"Installing the AWS plugin"</span>)

    <span class="hljs-comment">// for inline source programs, we must manage plugins ourselves</span>
    err = workspace.InstallPlugin(ctx, <span class="hljs-string">"aws"</span>, <span class="hljs-string">"v6.22.0"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to install program plugins: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

    fmt.Println(<span class="hljs-string">"Successfully installed AWS plugin"</span>)

    cfg := auto.ConfigMap{
        <span class="hljs-string">"aws:region"</span>:             auto.ConfigValue{Value: <span class="hljs-string">"us-east-1"</span>},
        <span class="hljs-string">"company:ticket"</span>:          auto.ConfigValue{Value: <span class="hljs-string">"CLOUD"</span>},
        <span class="hljs-string">"company:hubname"</span>: auto.ConfigValue{Value: <span class="hljs-string">"MYHUB"</span>},
    }

    createdStack.SetAllConfig(ctx, cfg)

    _, err = createdStack.Refresh(ctx)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to refresh stack: %v\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

    <span class="hljs-comment">// wire up our update to stream progress to stdout</span>
    stdoutStreamer := optup.ProgressStreams(os.Stdout)

    <span class="hljs-comment">// run the update to deploy our EC2 Cluster</span>
    _, err = createdStack.Up(ctx, stdoutStreamer)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Printf(<span class="hljs-string">"Failed to update stack: %v\n\n"</span>, err)
        os.Exit(<span class="hljs-number">1</span>)
    }

}
</code></pre>
<p>I hope this Blog post is helpful to you and shows you how Auto PKG works with Go in Pulumi.</p>
<p>Till next time :)</p>
]]></content:encoded></item><item><title><![CDATA[Using the Brave Search API with Go]]></title><description><![CDATA[Recently, I wanted to build a small program to search things through the Brave API. Why? Well, I wanted to begin to understand how APIs work and, two, how search engines work. So, I set out to set up my brave account and make an API key. This is writ...]]></description><link>https://theclouddude.co.uk/using-the-brave-search-api-with-go</link><guid isPermaLink="true">https://theclouddude.co.uk/using-the-brave-search-api-with-go</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Brave Browser]]></category><category><![CDATA[Developer]]></category><category><![CDATA[APIs]]></category><category><![CDATA[golang]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 16 Nov 2023 13:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696346966162/83121fb4-cd2a-4fa8-a237-bf31f92b0280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I wanted to build a small program to search things through the Brave API. Why? Well, I wanted to begin to understand how APIs work and, two, how search engines work. So, I set out to set up my brave account and make an API key. This is written here if you wish to follow along. https://tinyurl.com/mrxz873w</p>
<p><strong>Saying Hello to the Brave API</strong></p>
<p>I first tried the curl command to see how the API worked. (Just a side note for the sake of this blog: the command is on two lines to make the curl command work: move the <code>-H "Accept-Encoding: gzip" -H "X-Subscription-Token: API_KEY_VALUE"</code> up a level to make the command all one line.)</p>
<pre><code class="lang-plaintext">curl -s --compressed "https://api.search.brave.com/res/v1/web/search?q=brave+search" -H "Accept: application/json"
    -H "Accept-Encoding: gzip" -H "X-Subscription-Token: API_KEY_VALUE"
</code></pre>
<p>As you can see, the Headers are not the normal restful headers when using an API. For example, most restful headers accept the following Bearer format when working with a token. <code>-H "Authorization: Bearer &lt;ACCESS_TOKEN&gt;"</code></p>
<p>Using this Curl command will only get you authenticated to the API and will return the following: (This is just a snippet of what is returned; the whole JSON is very long and would make this a very long article.)</p>
<pre><code class="lang-plaintext">{"query":{"original":"brave search","show_strict_warning":false,"is_navigational":true,"local_decision":"drop","local_locations_idx":0,"is_news_breaking":false,"spellcheck_off":true,"country":"us","bad_results":false,"should_fallback":false,"postal_code":"","city":"","header_country":"","more_results_available":true,"state":""},"mixed":{"type":"mixed","main":[{"type":"web","index":0,"all":false},{"type":"web","index":1,"all":false},{"type":"videos","all":true},{"type":"web","index":2,"all":false},{"type":"web","index":3,"all":false},{"type":"web","index":4,"all":false},{"type":"web","index":5,"all":false},{"type":"web","index":6,"all":false},{"type":"web","index":7,"all":false},{"type":"web","index":8,"all":false},{"type":"web","index":9,"all":false},{"type":"web","index":10,"all":false},{"type":"web","index":11,"all":false},{"type":"web","index":12,"all":false},{"type":"web","index":13,"all":false},{"type":"web","index":14,"all":false},{"type":"web","index":15,"all":false},{"type":"web","index":16,"all":false},{"type":"web","index":17,"all":false},{"type":"web","index":18,"all":false},{"type":"web","index":19,"all":false}],"top":[],"side":[]},"type":"search","videos":{"type":"videos","results":[{"type":"video_result","url":"https://www.youtube.com/watch?v=gwho1EuwkRQ","title":"How To Change The Default Search Engine In The Brave ...","description":"How To Change The Default Search Engine In The Brave Web Browser | PC | *2022*This is a video tutorial on how to change the default search engine in the Brav...","age":"September 20, 2022","video":{},"meta_url":{"scheme":"https","netloc":"youtube.com","hostname":"www.youtube.com","favicon":"https://imgs.search.brave.com/Ux4Hee4evZhvjuTKwtapBycOGjGDci2Gvn2pbSzvbC0/rs:fit:32:32:1/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvOTkyZTZiMWU3/YzU3Nzc5YjExYzUy/N2VhZTIxOWNlYjM5/ZGVjN2MyZDY4Nzdh/ZDYzMTYxNmI5N2Rk/Y2Q3N2FkNy93d3cu/eW91dHViZS5jb20v","path":"› watch"},"thumbnail":{"src":"https://imgs.search.brave.com/6g23ttPyzgvCVreUZ2pcnuTnGAhNHt8yQLyVNL3mlsc/rs:fit:200:200:1/g:ce/aHR0cHM6Ly9pLnl0/aW1nLmNvbS92aS9n/d2hvMUV1d2tSUS9t/YXhyZXNkZWZhdWx0/LmpwZz9zcXA9LW9h/eW13RW1DSUFLRU5B/RjhxdUtxUU1hOEFF/Qi1BSC1DWUFDMEFX/S0Fnd0lBQkFCR0Vn/Z0V5aF9NQTg9JmFt/cDtycz1BT240Q0xB/dzdIUkZsVTd1OUZT/YUR2WERZSE1TNnNj/U0p3"}},{"type":"video_result","url":"https://www.youtube.com/watch?v=ux4VacF3hxc","title":"Brave Browser And Search Engine Keep Getting Better - YouTube",
</code></pre>
<p><strong>Lets Get Searching With Go</strong></p>
<p>As you can see, we have yet to get searching. For that, we turn to Go.</p>
<p>In Go, you can use specific packages to interact with APIs. They are: "encoding/json", "net/http", and "net/url." There are a few others we are going to use, but they are just there to make this all work. I won't go into detail about them here; maybe in a future blog post.</p>
<p>Essentially, what we are dealing with here is <code>JSON</code> being passed to us, and we have to do something with that JSON. Now you have structured JSON and Unstructured JSON. To learn more about the difference and more about JSON, there is a great article on the Go Blog here: https://go.dev/blog/json</p>
<p>As we know the fields we are getting from the CURL command, we know to use a structured JSON approach. To do that, we set structs to match the fields of the Returned JSON. We do that like this:</p>
<pre><code class="lang-plaintext">type Searchrequest struct {
    base       *url.URL
    token      string
    searchterm string
}

type Main struct {
    Type  string `json:"type"`
    Index int    `json:"index"`
    All   bool   `json:"all"`
}

type Query struct {
    Original             string `json:"original"`
    ShowStrictWarning    bool   `json:"show_strict_warning"`
    IsNavigational       bool   `json:"is_navigational"`
    IsNewsBreaking       bool   `json:"is_news_breaking"`
    SpellcheckOff        bool   `json:"spellcheck_off"`
    Country              string `json:"country"`
    BadResults           bool   `json:"bad_results"`
    ShouldFallback       bool   `json:"should_fallback"`
    PostalCode           string `json:"postal_code"`
    City                 string `json:"city"`
    HeaderCountry        string `json:"header_country"`
    MoreResultsAvailable bool   `json:"more_results_available"`
    State                string `json:"state"`
}

type Mixed struct {
    Type string        `json:"type"`
    Main []Main        `json:"main"`
    Top  []interface{} `json:"top"`
    Side []interface{} `json:"side"`
}

type MetaURL struct {
    Scheme   string `json:"scheme"`
    Netloc   string `json:"netloc"`
    Hostname string `json:"hostname"`
    Favicon  string `json:"favicon"`
    Path     string `json:"path"`
}

type Results struct {
    Title          string  `json:"title"`
    URL            string  `json:"url"`
    IsSourceLocal  bool    `json:"is_source_local"`
    IsSourceBoth   bool    `json:"is_source_both"`
    Description    string  `json:"description"`
    Language       string  `json:"language"`
    FamilyFriendly bool    `json:"family_friendly"`
    Type           string  `json:"type"`
    Subtype        string  `json:"subtype"`
    MetaURL        MetaURL `json:"meta_url"`
    Age            string  `json:"age,omitempty"`
}

type Web struct {
    Type           string    `json:"type"`
    Results        []Results `json:"results"`
    FamilyFriendly bool      `json:"family_friendly"`
}

type Response struct {
    Query Query  `json:"query"`
    Mixed Mixed  `json:"mixed"`
    Type  string `json:"type"`
    Web   Web    `json:"web"`
}
</code></pre>
<p>This gets us ready to receive and send data and do something with it. We will touch on that in a minute first to tell Go to go (no pun intended) and interact with the API. We have to do what I like to call building the request.</p>
<p><strong>Let's Build our Request</strong></p>
<p>To build the request, we use the "net/http" package. We essentially tell the package this is going to be a GET request with a token, and in the "net/http" package, there is a function called <code>http.NewRequest</code> this basically controls our request and allows us to pass certain parameters to the function. We use <code>http.MethodGet</code> to be able to pass in a token and our search query.</p>
<p>The following code builds our request:</p>
<pre><code class="lang-plaintext">req, err := http.NewRequest(http.MethodGet, encodedurl, nil)
    if err != nil {
        log.Fatal(err)
    }
    req.Header = http.Header{
        "Accept":               {"application/json"},
        "X-Subscription-Token": {searchrequest.token},
    }
</code></pre>
<p>As you can see, our <code>req</code> variable receives the encoded URL (we will touch on this after and nil for error handling). We then pass the variable into a struct and add the following two headers "Accept:" which is how we wish to receive our data in this instance <code>json</code>.</p>
<p>The other header <code>"X-Subscription-Token"</code> is the token we are passing in. We are getting this token from a flag when we run the program as it is not good practice to put API keys into your code; we pass the value from this flag into a struct field called <code>searchrequest.token</code></p>
<p><strong>Passing in Flags</strong></p>
<p>This piece of code is at the beginning of our program and looks like this:</p>
<pre><code class="lang-plaintext">    // Ask For Token and Search Term
    searchrequest := Searchrequest{
        token:      "",
        searchterm: "",
    }

    flag.StringVar(&amp;searchrequest.token, "token", "", "token")
    flag.StringVar(&amp;searchrequest.searchterm, "searchterm", "", "searchterm")
    flag.Parse()
</code></pre>
<p>This asks our user for the token and the search term. Input is as follows:</p>
<pre><code class="lang-plaintext">go run getwebpage.go -token "VALUE" -searchterm "VALUE"
</code></pre>
<p><strong>Working with Search Engines Search Terms</strong></p>
<pre><code class="lang-plaintext">    encodedsearchterm := url.PathEscape(searchrequest.searchterm)

    encodedurl := fmt.Sprintf("https://api.search.brave.com/res/v1/web/search")
    queryparam := url.Values{}
    queryparam.Set("q", encodedsearchterm)
    if len(searchrequest.searchterm) &gt; 0 {
        encodedurl += "?" + queryparam.Encode()
    }
</code></pre>
<p>The reason why you see in the code the following variable <code>encodedurl</code> is not because of security but because of a simple problem when working with search engines. When you enter a search term into the search engine, you put your term like this ** Show Me Some Cats**. As you can see, you put your text in with spaces.</p>
<p>Now, the search engine automatically encodes your search term to something like this: <code>show%20me%20some%20cats</code>.This tells the rest of the engine this is a whole string with spaces.</p>
<p>Using the "net/url" package, you can encode the term first by passing it to the <code>url</code> struct called <code>url.Values{}</code> (this is in the net/url package). Then you get the search query and pass it to <code>url.Values{}</code> but not before you have encoded it with the <code>Encode</code> function. Now, for this to work, you also have to attach it to the "q" header, as this is what the Brave API only accepts to be able to use your term to search. The code that attaches our encoded query to the header of "q" is <code>queryparam.Set("q", encodedsearchterm)</code></p>
<p><strong>Finishing our Request</strong></p>
<p>Now we have the API token and the query (our search term), we build a client to handle our request and then pass the <code>req</code> variable to that client.</p>
<pre><code class="lang-plaintext">// Send Request
    client := &amp;http.Client{}
    repsonse, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer repsonse.Body.Close()
</code></pre>
<p><strong>Receiving The Request</strong></p>
<p>Essentially, by this point, all I wanted to do was receive my data and write to a file in the format of JSON.</p>
<p>To do that, I used the "json/unmarshall" package, and in this piece of code, I received the JSON and unmarshal it: <code>json.Unmarshal([]byte(body), &amp;res)</code></p>
<p>Then, this code will indent my JSON so that It is semi-readable, and it will write it to a file in the same directory as my program and call it reply.json.</p>
<pre><code class="lang-plaintext">content := fmt.Sprintf("Query: %+v\n, Results: %+v\n", res.Query, res.Web.Results)
    bytes, _ := json.MarshalIndent(content, "", "")
    contentjsonerr := os.WriteFile("reply.json", bytes, 0644)
    if contentjsonerr != nil {
        log.Fatal(contentjsonerr)
    }
</code></pre>
<p>You can do a ton with that data, but this Blog post is already long enough, so I will stop here. I think I might write another post soon about what we can do with our cat's data.</p>
<p>If you want to write this program or run it yourself, you can find the code here: https://github.com/jasric89/headfirstgo/tree/master/Chapter13/HTTPProg</p>
<p>I wish to thank the Slack Gopher community (Join here: gophers.slack.com), particularly a fellow Gopher called Peter Hellberg, who helped me understand how APIs work. Without him, this blog post would not exist.</p>
<p>Happy Coding and The Cloud Dude will see you on the next Post.</p>
]]></content:encoded></item><item><title><![CDATA[Head First Go Book Review]]></title><description><![CDATA[I have recently just finished reading Head First Go by Jay McGavren. When I opened the book, I thought this was a very different way of learning to program from a book.  
It's a bit like a school workbook; you have exercises where you need to use a p...]]></description><link>https://theclouddude.co.uk/head-first-go-book-review</link><guid isPermaLink="true">https://theclouddude.co.uk/head-first-go-book-review</guid><category><![CDATA[Go Language]]></category><category><![CDATA[#BookReview]]></category><category><![CDATA[golang]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 12 Oct 2023 12:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695118795900/2e9de224-ec80-4039-bfc9-f73b2e424fec.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695038069796/436796df-12fc-4d2b-91ac-c49c350f9bfd.jpeg" alt class="image--center mx-auto" /></p>
<p>I have recently just finished reading Head First Go by Jay McGavren. When I opened the book, I thought this was a very different way of learning to program from a book.  </p>
<p>It's a bit like a school workbook; you have exercises where you need to use a pen or pencil for, and then there are also times when you need to build what they are writing about. My Github Repo for this book is here: <a target="_blank" href="https://github.com/jasric89/headfirstgo">https://github.com/jasric89/headfirstgo</a> if you wish to take a look.</p>
<p>I have read quite a few books on Go, and honestly, I get halfway through the book and give up. The reason for this was I had spent the last two years trying to grasp the language, and no book for me personally could do that until I got to this book.  </p>
<p>The way Jay McGavren writes is like someone guiding you through the language. Each chapter creates an interesting problem that naturally, your brain kicks in and goes ohh, I wonder how this problem is solved. It's sort of like a Sherlock Holmes episode of who committed the crime. Or an NCIS series, depending on your taste. (If you haven't already figured it out, I like crime series.)  </p>
<p>Then there are the exercises where you have to get a pen out and fill in the blanks, which, in an interesting way, really starts cementing what you are learning. For me, when this first started happening. I had to take a moment with a smile and realised oh wait, this is actually going in.  </p>
<p>As I said, there are tons of books I have read on Go where you read the chapter, then the Author leaves you an exercise to complete, which really most of that exercise has no real bearing on what you were reading, then you spend most of your time on Stack Overflow trying to figure out how to complete the exercise. Then you get disheartened about why you can't do it; the book gets tossed to the side and back to the drawing board. <strong>Not with this book.</strong>  </p>
<p>The only major drawback with this book is that it was written before modules came out, so essentially, it uses GoPath for a lot of the book. I did head to their website, <a target="_blank" href="https://headfirstgo.com/">https://headfirstgo.com/</a>, when I first noticed this problem, but I could not see any notice about the issue or any other versions of the chapters released with the new Go Pkg framework. </p>
<p>At first, this was frustrating because I could not get my head around how Packages work in Go. Yet I was already so impressed with the book that I spent two to three hours more learning how to write packages in Go. I have also written a blog post on it here: <a target="_blank" href="https://theclouddude.co.uk/mastering-writing-packages-with-go-when-working-with-the-azure-sdk">https://theclouddude.co.uk/mastering-writing-packages-with-go-when-working-with-the-azure-sdk</a>  </p>
<p>This may save you the hours of figuring out how packages work if you give that post a read and then apply it to the parts of the book where GoPath is being spoken about. It could be enough, though, to put the person off buying this book because, essentially, that part is so out of date.</p>
<p>There is also another area where things are out of date, and that is with the <code>ioutil</code> package that's been depreciated in Go. It's now the <code>io</code> or <code>os</code> package. Information about this is given here: <a target="_blank" href="https://pkg.go.dev/io/ioutil">https://pkg.go.dev/io/ioutil</a>  </p>
<p>Other than those things, this book, in my opinion, is very good. It teaches you the Language in a way you remember; you are not lost and disheartened when something does not work, which I think when starting out with a Language is one of the most important aspects.  </p>
<p>I would say this book is very good for Beginners to Go; if you have come from another Language, you may start skimming through the book, which in turn would actually hinder you from finishing it properly and getting the programs working. I guess I am saying that this book is for you if you have basic or no programming knowledge. If you are a seasoned Developer in another language, perhaps choose another book. I think this one is very good for seasoned developers. <a target="_blank" href="https://www.gopl.io/">https://www.gopl.io/</a>  </p>
<p>Overall, I enjoyed reading this book; it was easy reading, did not send me to sleep, made me confident in the Go Language, and learned a lot. I highly recommend this book if you can see past its outdated issues.</p>



]]></content:encoded></item><item><title><![CDATA[Mastering writing Packages with Go when working with the Azure SDK.]]></title><description><![CDATA[Please note I am Dyslexic, so if things don't make grammatical sense, please do not hold it against me. Be Kind
One thing you need to know well when working with the Go Azure SDK is how packages work with the removal of GOPATH.
This has taken me some...]]></description><link>https://theclouddude.co.uk/mastering-writing-packages-with-go-when-working-with-the-azure-sdk</link><guid isPermaLink="true">https://theclouddude.co.uk/mastering-writing-packages-with-go-when-working-with-the-azure-sdk</guid><category><![CDATA[Azure]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Golang developer]]></category><category><![CDATA[packages]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 14 Sep 2023 12:00:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1694601073915/e4ca4a27-0d9a-45d2-bceb-3402b2c032f9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Please note I am Dyslexic, so if things don't make grammatical sense, please do not hold it against me. Be Kind</em></p>
<p>One thing you need to know well when working with the Go Azure SDK is how packages work with the removal of GOPATH.</p>
<p>This has taken me some time to get my head around.</p>
<p>Since Go 1.11 there was a fundamental change with the Go Compiler. GOPATH was gone; no more building your Go Projects in your machine's src/bin folder. Now, you can build your Go projects anywhere. Yay, I hear many of you say. Well, at first, when learning Go, I thought that made sense until I started messing about with making and importing packages.</p>
<p>The way making packages work is you must run <code>go mod init module_name</code> first. It's not such a big deal and easy enough to master.</p>
<p>Until you get to the point of wanting to import that package.</p>
<p>Take this example, for instance:</p>
<pre><code class="lang-plaintext">┣ 📂pkgwork
┃ ┗ 📂mypkg
┃   ┗ 📜mypkg.go
┣ 📜go.mod
┗ 📜packagework.go
</code></pre>
<p>Here, I have made a package called <code>packagework</code>, which just sits outside the folder <code>pkgwork</code>.</p>
<p>I tried to import the package like this in my code:</p>
<pre><code class="lang-plaintext">package main

import (
    "fmt"
    mypkg "pkgwork/mypkg"
)

func main() {
    var value mypkg.MyInterface
    value = mypkg.MyType(5)
    value.MethodWithoutParameters()
    value.MethodWithParamaters(127.3)
    fmt.Println(value.MethodWithReturnValue())

}
</code></pre>
<p>In my head, that makes sense because I'm thinking Well, the root of my package is <code>pkgwork/mypkg</code></p>
<p>Yet, when I run <code>go run packagework.go</code> I get the following error:</p>
<p><code>package pkgwork/mypkg is not in GOROOT (/usr/local/go/src/pkgwork/mypkg)</code></p>
<p>This means the GO compiler went to look for my package within the module and expected my module name, <code>pkgwork</code>, to be in the pkgwork folder. Yet my <code>module_name</code> in this instance, is not called <code>pkgwork</code> it's called <code>packagework</code>.</p>
<p>Once Go realised that the module was not in this destination, what it did was It went outside the <code>pkgwork</code> folder. Therefore, its next course of action is to go and check the main Go libraries in the GOROOT folder. When all places are searched and not found, it comes back with the error we just saw.</p>
<p>Essentially, my understanding of importing packages was wrong, and I have seen many others think the same way. To import your package, it's not <code>package_path/package_name</code> it's <code>module_name/package_name</code></p>
<p>To fix this and to make this easier to understand in my own head. I put my main go file called <code>packagework.go</code> in a folder called <code>pkgwork</code>, so my folder structure looks like this:</p>
<pre><code class="lang-plaintext">┗ 📂pkgwork
  ┣ 📂mypkg
  ┃ ┗ 📜mypkg.go
  ┣ 📜go.mod
  ┗ 📜packagework.go
</code></pre>
<p>I re-modulated my project with the <code>module_name</code> of <code>packagework</code> and then imported that into my <code>packagework.go</code> file like this:</p>
<p><code>mypkg "packagework/mypkg"</code></p>
<p>So essentially, my main Go file looks like this:</p>
<pre><code class="lang-plaintext">package main

import (
    "fmt"
    mypkg "packagework/mypkg"
)

func main() {
    var value mypkg.MyInterface
    value = mypkg.MyType(5)
    value.MethodWithoutParameters()
    value.MethodWithParamaters(127.3)
    fmt.Println(value.MethodWithReturnValue())

}
</code></pre>
<p>This then allowed Go to find the package I was referring to.</p>
<p>One other thing I have picked up whilst learning Go is labelling my packages when importing them; if you noticed, I import like this: <code>mypkg "packagework/mypkg"</code> which is <code>label module_name/pkgname</code></p>
<p>I find it is then much easier to call to my package when coding in main; it's also easier to read. Just a little hint.</p>
<p>Now, you have come down this far, and I have not mentioned the Azure SDK once, so rightly you are thinking, why is this important to the Azure SDK?</p>
<p>Well, what I have found when working with the Azure SDK is everything is split up into packages, which are essentially modules with pkg names. Let's look at the main one you will find yourself working with a lot:</p>
<p>The <code>azidentity</code> package https://tinyurl.com/azidentity. This package is used to authenticate into Azure. To work with it, you have to import it into your project like this:</p>
<p><code>"github.com/Azure/azure-sdk-for-go/sdk/azidentity"</code></p>
<p>And you reference it like this: without a label, you take the end part of the full path, so in this instance, <code>azindentity</code> Here is a small example:</p>
<pre><code class="lang-plaintext">azCLI, err := azidentity.NewAzureCLICredential(nil)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("Authenticated!!")
        armcompute.NewVirtualMachinesClient(subscriptionID, azCLI, nil)
    }
</code></pre>
<p>On another note, the Github.com URLS are no different to just calling your module_name something like <code>packagework</code>. Essentially, Go does not see them as URLS when you are first making your packages, so If I call my module github.com/jasric89/packagework, then Go will see that as a name. Only when it goes into GitHub does it become something to download as a package for another developer using the <code>go get -u URL to package_name.</code></p>
<p>I hope this helps someone. It took me a while to get my head around this subject.</p>
]]></content:encoded></item><item><title><![CDATA[Azure VM Extensions, written in Go]]></title><description><![CDATA[Please note I am Dyslexic, so if things don't make grammatical sense, please do not hold it against me. Be Kind
There is a project I was recently involved in where they wanted a VM in Azure running IIS. If you don't wish to do this with Go Lang, you ...]]></description><link>https://theclouddude.co.uk/azure-vm-extensions-written-in-go</link><guid isPermaLink="true">https://theclouddude.co.uk/azure-vm-extensions-written-in-go</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Azure]]></category><category><![CDATA[go]]></category><category><![CDATA[virtual machine]]></category><category><![CDATA[Go Lang azure sdk]]></category><dc:creator><![CDATA[Jason]]></dc:creator><pubDate>Thu, 24 Aug 2023 11:00:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1692703192788/bedc3aa9-9c54-4b51-9781-b4f221a580a4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Please note I am Dyslexic, so if things don't make grammatical sense, please do not hold it against me. Be Kind</em></p>
<p>There is a project I was recently involved in where they wanted a VM in Azure running IIS. If you don't wish to do this with Go Lang, you can follow this Blog post by Facundo Gauna. He explains how to do this with PowerShell: https://gaunacode.com/install-iis-on-azure-vm-using-terraform</p>
<p>This was also a learning exercise to see how Go interacts with the Azure SDK; I've been learning and using Go for around a year, and the saying. "Go is easy to learn but hard to master." It could not be more true. So please, if you don't wish to go on this learning exercise and are thinking well, this can just be done with Powershell, then be my guest in following that link ;). Now that we have housekeeping out of the way let me explain what I did.</p>
<p><em>I made the VM using Terraform as this blog post is not about Terraform, but Go I will leave this link here to make a VM using Terraform.</em>  </p>
<p>https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/windows_virtual_machine.html</p>
<p>Our Journey begins at the <code>NewVirtualMachineExtensionsClient</code> function from the package armcompute (links at the bottom of the post for more information): <code>NewVirtualMachineExtensionsClient(AzureSubscriptionID, cred, nil)</code>  This function tells the Azure API that I am about to create a new Virtual Machine Extension function and here are my Subscription ID and Credentials. This function does return an error, the way I handled the error is with the following piece of code:</p>
<pre><code><span class="hljs-keyword">if</span> err != nil {
        log.Fatalf(<span class="hljs-string">"failed to create client: %v"</span>, err)
    }
</code></pre><p>This is <strong>simple</strong> error handling. The Program will terminate and tell me the whole error right from the SDK. (<strong>One Tip:</strong> When working with the Azure SDK, don't write custom error messages; the SDK will forward detailed error messages for you, making researching these errors much easier.) </p>
<p>Here's the whole function in its entirety:</p>
<pre><code>client, <span class="hljs-attr">err</span> := armcompute.NewVirtualMachineExtensionsClient(AzureSubscriptionID, cred, nil)
    <span class="hljs-keyword">if</span> err != nil {
        log.Fatalf(<span class="hljs-string">"failed to create client: %v"</span>, err)
    }
</code></pre><p>The first major hurdle I found in this journey is the following function, <code>BeginCreateOrUpdate</code>, actually updating the VM extension. The docs give an example of how this function is used, but when you fill the fields in with pointers, you will run into all sorts of errors with the SDK talking to Microsoft Azure. </p>
<p>What I found was that actually, it was not the best example of working with an extensions script; in researching for a few hours, I came across this Microsoft Article here: 
https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-deploy-vm-extensions#:~:text=Save%20As.-,Edit%20the%20template,-Add%20a%20virtual</p>
<p>I then edited my calls to the struct.<code>VirtualMachineExtensionsClient</code>, which I believe all these fields were in the example JSON file given in the MS document. Here is an example of that JSON:</p>
<pre><code>{
  <span class="hljs-string">"type"</span>: <span class="hljs-string">"Microsoft.Compute/virtualMachines/extensions"</span>,
  <span class="hljs-string">"apiVersion"</span>: <span class="hljs-string">"2021-04-01"</span>,
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"[format('{0}/{1}', variables('vmName'), 'InstallWebServer')]"</span>,
  <span class="hljs-string">"location"</span>: <span class="hljs-string">"[parameters('location')]"</span>,
  <span class="hljs-string">"dependsOn"</span>: [
    <span class="hljs-string">"[format('Microsoft.Compute/virtualMachines/{0}',variables('vmName'))]"</span>
  ],
  <span class="hljs-string">"properties"</span>: {
    <span class="hljs-string">"publisher"</span>: <span class="hljs-string">"Microsoft.Compute"</span>,
    <span class="hljs-string">"type"</span>: <span class="hljs-string">"CustomScriptExtension"</span>,
    <span class="hljs-string">"typeHandlerVersion"</span>: <span class="hljs-string">"1.7"</span>,
    <span class="hljs-string">"autoUpgradeMinorVersion"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-string">"settings"</span>: {
      <span class="hljs-string">"fileUris"</span>: [
        <span class="hljs-string">"https://raw.githubusercontent.com/Azure/azure-docs-json-samples/master/tutorial-vm-extension/installWebServer.ps1"</span>
      ],
      <span class="hljs-string">"commandToExecute"</span>: <span class="hljs-string">"powershell.exe -ExecutionPolicy Unrestricted -File installWebServer.ps1"</span>
    }
  }
}
</code></pre><p>As you can see from my code, I am now writing the following to the struct <code>VirtualMachineExtensionProperties</code> through a pointer using the <code>armcompute</code> package: </p>
<pre><code>Properties: &amp;armcompute.VirtualMachineExtensionProperties{
            <span class="hljs-attr">Type</span>:                    to.Ptr(<span class="hljs-string">"CustomScriptExtension"</span>),
            <span class="hljs-attr">AutoUpgradeMinorVersion</span>: to.Ptr(<span class="hljs-literal">true</span>),
            <span class="hljs-attr">ForceUpdateTag</span>:          to.Ptr(<span class="hljs-string">"False"</span>),
            <span class="hljs-attr">InstanceView</span>: &amp;armcompute.VirtualMachineExtensionInstanceView{
                <span class="hljs-attr">Name</span>: to.Ptr(<span class="hljs-string">"InstallWebServer"</span>),
                <span class="hljs-attr">Type</span>: to.Ptr(<span class="hljs-string">"CustomScriptExtension"</span>),
            },
            <span class="hljs-attr">Publisher</span>: to.Ptr(<span class="hljs-string">"Microsoft.Compute"</span>),
            <span class="hljs-attr">Settings</span>: map[string]interface{}{
                <span class="hljs-string">"commandToExecute"</span>: <span class="hljs-string">"powershell -ExecutionPolicy Unrestricted Install-WindowsFeature -Name Web-Server -IncludeAllSubFeature -IncludeManagementTools"</span>,
            },
            <span class="hljs-attr">SuppressFailures</span>:   to.Ptr(<span class="hljs-literal">true</span>),
            <span class="hljs-attr">TypeHandlerVersion</span>: to.Ptr(<span class="hljs-string">"1.7"</span>),
        },
</code></pre><p>What I have found to be quite hard when working with Azure SDK is that there are many nested structs within structs. The struct fields themselves are based on JSON files. I do believe these are essentially just ARM templates being called. It's actually very hard to work out in the first instance how to change a property of a struct in the Azure SDK. </p>
<p>For example, the current function we are working on requires us first to pass in: <code>armcompute.VirtualMachineExtension</code>, which in itself is a struct from the package <code>armcompute</code>. Then because the struct we actually want to edit is <code>VirtualMachineExtensionProperties</code>, you have to do some crazy pointer work to edit those fields like this: </p>
<pre><code>Type:                    to.Ptr(<span class="hljs-string">"CustomScriptExtension"</span>),
            <span class="hljs-attr">AutoUpgradeMinorVersion</span>: to.Ptr(<span class="hljs-literal">true</span>),
            <span class="hljs-attr">ForceUpdateTag</span>:          to.Ptr(<span class="hljs-string">"False"</span>),
</code></pre><p> Miki Tebeka (Who has written the book Go Brain Teasers") showed me another way to do this, which I may try another time. For now, this works. </p>
<p>Essentially to make a VM Extension using Go you are left with the following Code. I hope I have explained various parts of this code well enough for you to understand what is happening. </p>
<pre><code>package main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"context"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>

    <span class="hljs-string">"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"</span>
    <span class="hljs-string">"github.com/Azure/azure-sdk-for-go/sdk/azidentity"</span>
    <span class="hljs-string">"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4"</span>
)

func main() {
    <span class="hljs-attr">AzureSubscriptionID</span> := <span class="hljs-string">"VALUE"</span>
    <span class="hljs-attr">myVM</span> := <span class="hljs-string">"vm-onpremvm"</span>
    <span class="hljs-attr">myVMLocation</span> := <span class="hljs-string">"North Europe"</span>
    <span class="hljs-attr">resourceGroup</span> := <span class="hljs-string">"RG-JC-Sandbox"</span>
    <span class="hljs-attr">myVMExtension</span> := <span class="hljs-string">"IIS"</span>
    <span class="hljs-comment">// Authenticate To Azure</span>

    azCLI, <span class="hljs-attr">err</span> := azidentity.NewAzureCLICredential(nil)
    <span class="hljs-keyword">if</span> err != nil {
        fmt.Println(err)
    } <span class="hljs-keyword">else</span> {
        fmt.Println(<span class="hljs-string">"Authenticated!!"</span>)
    }
    <span class="hljs-attr">cred</span> := azCLI
    <span class="hljs-attr">ctx</span> := context.Background()
    <span class="hljs-comment">// Edit the VM Extension</span>
    client, <span class="hljs-attr">err</span> := armcompute.NewVirtualMachineExtensionsClient(AzureSubscriptionID, cred, nil)
    <span class="hljs-keyword">if</span> err != nil {
        log.Fatalf(<span class="hljs-string">"failed to create client: %v"</span>, err)
    }
    poller, <span class="hljs-attr">err</span> := client.BeginCreateOrUpdate(ctx, resourceGroup, myVM, myVMExtension, armcompute.VirtualMachineExtension{
        <span class="hljs-attr">Location</span>: to.Ptr(myVMLocation),
        <span class="hljs-attr">Tags</span>: map[string]*string{
            <span class="hljs-string">"IISExtension"</span>: to.Ptr(<span class="hljs-string">"IISExtension"</span>),
        },
        <span class="hljs-attr">Properties</span>: &amp;armcompute.VirtualMachineExtensionProperties{
            <span class="hljs-attr">Type</span>:                    to.Ptr(<span class="hljs-string">"CustomScriptExtension"</span>),
            <span class="hljs-attr">AutoUpgradeMinorVersion</span>: to.Ptr(<span class="hljs-literal">true</span>),
            <span class="hljs-attr">ForceUpdateTag</span>:          to.Ptr(<span class="hljs-string">"False"</span>),
            <span class="hljs-attr">InstanceView</span>: &amp;armcompute.VirtualMachineExtensionInstanceView{
                <span class="hljs-attr">Name</span>: to.Ptr(<span class="hljs-string">"InstallWebServer"</span>),
                <span class="hljs-attr">Type</span>: to.Ptr(<span class="hljs-string">"CustomScriptExtension"</span>),
            },
            <span class="hljs-attr">Publisher</span>: to.Ptr(<span class="hljs-string">"Microsoft.Compute"</span>),
            <span class="hljs-attr">Settings</span>: map[string]interface{}{
                <span class="hljs-string">"commandToExecute"</span>: <span class="hljs-string">"powershell -ExecutionPolicy Unrestricted Install-WindowsFeature -Name Web-Server -IncludeAllSubFeature -IncludeManagementTools"</span>,
            },
            <span class="hljs-attr">SuppressFailures</span>:   to.Ptr(<span class="hljs-literal">true</span>),
            <span class="hljs-attr">TypeHandlerVersion</span>: to.Ptr(<span class="hljs-string">"1.7"</span>),
        },
    },
        nil)
    <span class="hljs-keyword">if</span> err != nil {
        log.Fatalf(<span class="hljs-string">"failed to finish the request: %v"</span>, err)
    }
    res, <span class="hljs-attr">err</span> := poller.PollUntilDone(ctx, nil)
    <span class="hljs-keyword">if</span> err != nil {
        log.Fatalf(<span class="hljs-string">"failed to pull the result: %v"</span>, err)
    }
    fmt.Printf(<span class="hljs-string">"%v"</span>, res)
}
</code></pre><p>I hope this has helped someone; this was my first ever blog post about Go; I hope you have enjoyed it. I am no Go expert. If you wish to get in touch with me I can be reached here at jason@theclouddude.co.uk Id like to say thank you for reading this far. :) </p>
<p>I would also like to thank the <strong>Gophers</strong> at the Gopher Slack found here: gophers.slack.com for helping me hugely with this project and pointing me in the right direction.</p>
<p>The Resources I used were: </p>
<p>https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-tutorial-deploy-vm-extensions</p>
<p>https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4#VirtualMachineExtensionsClient</p>
<p>https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4#ClientFactory.NewVirtualMachineExtensionsClient:~:text=README,ClientFactory)%20NewVirtualMachineExtensionsClient%20%C2%B6</p>
]]></content:encoded></item></channel></rss>