<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-AU" xmlns:media="http://search.yahoo.com/mrss/">
  <id>https://david.gardiner.net.au/tags/DevOps.xml</id>
  <title type="html">David Gardiner - DevOps</title>
  <updated>2026-05-19T00:35:57.922Z</updated>
  <subtitle>Blog posts tagged with &apos;DevOps&apos; - A blog of software development, .NET and other interesting things</subtitle>
  <rights>Copyright 2026 David Gardiner</rights>
  <icon>https://www.gravatar.com/avatar/37edf2567185071646d62ba28b868fab?s=64</icon>
  <logo>https://www.gravatar.com/avatar/37edf2567185071646d62ba28b868fab?s=256</logo>
  <generator uri="https://github.com/flcdrg/astrojs-atom" version="3">astrojs-atom</generator>
  <author>
    <name>David Gardiner</name>
  </author>
  <link href="https://david.gardiner.net.au/tags/DevOps.xml" rel="self" type="application/atom+xml"/>
  <link href="https://david.gardiner.net.au/tags/DevOps" rel="alternate" type="text/html" hreflang="en-AU"/>
  <category term="DevOps"/>
  <category term="Software Development"/>
  <entry>
    <id>https://david.gardiner.net.au/2025/01/biggest-problem</id>
    <updated>2025-01-07T12:30:00.000+10:30</updated>
    <title>The biggest problem with CI/CD pipelines</title>
    <link href="https://david.gardiner.net.au/2025/01/biggest-problem" rel="alternate" type="text/html" title="The biggest problem with CI/CD pipelines"/>
    <category term="DevOps"/>
    <published>2025-01-07T12:30:00.000+10:30</published>
    <summary type="html">Why CI/CD pipelines become fragile when they are hard to understand and safely change.</summary>
    <content type="html">&lt;p&gt;Do you know the most common problem I encounter when creating or updating build or deployment pipelines?&lt;/p&gt;
&lt;p&gt;Finding the correct path to specific files!&lt;/p&gt;
&lt;p&gt;I think that&apos;s why I find I have to litter my pipelines with extra steps like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- script: ls -alR
  displayName: &quot;Script: List files&quot;
  workingDirectory: $(Build.ArtifactStagingDirectory)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think the problem is really that tools used to build software with, and the tasks you use in your pipelines, are so inconsistent with how they generate and reference files and paths.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does the file generated by a tool get created in a single directory, or does it create a child directory for the file to live in?&lt;/li&gt;
&lt;li&gt;If you use wildcards with the tool, does that change how it generates the output file(s)?&lt;/li&gt;
&lt;li&gt;Does the tool default to looking for files in the current working directory or somewhere else?&lt;/li&gt;
&lt;li&gt;If you use relative paths for tool parameters, are they relative to the current working directory, or to another specific parameter?&lt;/li&gt;
&lt;li&gt;If you&apos;re creating a zip file by pointing to a directory, does it include that directory in the zip, or just the child files and directories?&lt;/li&gt;
&lt;li&gt;Does the tool or task support wildcards? And are they &apos;normal&apos; wildcards (aka globbing), or are they actually regular expressions?&lt;/li&gt;
&lt;li&gt;If the tool supports wildcards, can you specify multiple patterns, or only one?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;m sure there are other variations. You get the idea at least.&lt;/p&gt;
&lt;p&gt;And once you&apos;ve finished banging your head against the wall and got all your path &apos;ducks&apos; in a row, do you leave all those &lt;code&gt;ls -alR&lt;/code&gt; tasks in your pipelines just in case you might need to refer to them in the future, or do you remove them to get rid of the extra noise (and make things a tiny bit faster)?&lt;/p&gt;
&lt;p&gt;Using a tool like &lt;a href=&quot;https://cakebuild.net/&quot;&gt;Cake&lt;/a&gt; as an abstraction over all your tools may help to a certain extent, as it then provides a more consistent interface in how you use the tools. But even then I&apos;ve found myself having to add extra code in my .cake files to again list what files it can see to troubleshoot when things are not working as I think they should.&lt;/p&gt;
&lt;p&gt;It&apos;s such a trivial thing, but it continues to trip me up, and I suspect I&apos;m not alone 😀&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2023/12/migrate-terraform-resources-part2</id>
    <updated>2023-12-26T16:30:00.000+10:30</updated>
    <title>Migrating deprecated Terraform resources (part 2)</title>
    <link href="https://david.gardiner.net.au/2023/12/migrate-terraform-resources-part2" rel="alternate" type="text/html" title="Migrating deprecated Terraform resources (part 2)"/>
    <category term="Azure Pipelines"/>
    <category term="DevOps"/>
    <category term="Terraform"/>
    <published>2023-12-26T16:30:00.000+10:30</published>
    <summary type="html">Follows up on Terraform resource migration with common pitfalls and practical workarounds discovered during real upgrades.</summary>
    <content type="html">&lt;p&gt;In my &lt;a href=&quot;/2023/12/migrate-terraform-resources&quot;&gt;previous post&lt;/a&gt;, I showed how to migrate deprecated Terraform resources to a supported resource type. But I hinted that there are some gotchas to be aware of. Here are some issues that I have encountered and how to work around them.&lt;/p&gt;
&lt;h2&gt;Deploying to a new environment&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;import&lt;/code&gt; statement will fail if you try to deploy to a brand new empty environment. It makes sense when you think about it - how can you import a resource that doesn&apos;t exist yet? Unfortunately, there&apos;s no way to make the &lt;code&gt;import&lt;/code&gt; statement conditional.&lt;/p&gt;
&lt;p&gt;An example of this would be if your application has been in development for a while, and to assist in testing you now want to create a new UAT environment. There are no resources in the UAT environment yet, so the &lt;code&gt;import&lt;/code&gt; statement will fail.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Initializing plugins and modules...
data.azurerm_resource_group.group: Refreshing...
data.azurerm_client_config.client: Refreshing...
data.azurerm_client_config.client: Refresh complete after 0s [id=xxxxxxxxxxxxx=]
data.azurerm_resource_group.group: Refresh complete after 0s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast]
azurerm_service_plan.plan: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast]
╷
│ Error: Cannot import non-existent remote object
│
│ While attempting to import an existing object to
│ &quot;azurerm_service_plan.plan&quot;, the provider detected that no object exists
│ with the given id. Only pre-existing objects can be imported; check that
│ the id is correct and that it is associated with the provider&apos;s configured
│ region or endpoint, or use &quot;terraform apply&quot; to create a new remote object
│ for this resource.
╵
Operation failed: failed running terraform plan (exit 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Deploying to an environment where resources may have been deleted&lt;/h2&gt;
&lt;p&gt;This is similar to the previous scenario. It&apos;s one I&apos;ve encountered where we have non-production environments where you want to &quot;mothball&quot; when they&apos;re not being actively used to save money. We selectively delete resources, and then when the environment is needed we re-provision them. Again, the &lt;code&gt;import&lt;/code&gt; statement will fail if it is referencing a deleted resource.&lt;/p&gt;
&lt;h2&gt;A Solution&lt;/h2&gt;
&lt;p&gt;The workaround to support these scenarios is to not use the &lt;code&gt;import&lt;/code&gt; statement in your HCL code, but instead use the &lt;code&gt;terraform import&lt;/code&gt; command. Because we&apos;re calling the command from the command line, we can make it conditional. e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if az appservice plan show --name plan-tfupgrade-australiasoutheast --resource-group $ARM_RESOURCE_GROUP --query id --output tsv &amp;gt; /dev/null 2&amp;gt;&amp;amp;1; then
  terraform import azurerm_service_plan.plan /subscriptions/$ARM_SUBSCRIPTION_ID/resourceGroups/$ARM_RESOURCE_GROUP/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast
else
  echo &quot;Resource plan-tfupgrade-australiasoutheast does not exist in Azure&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Repeat this pattern for all resources that need to be imported. Also, take care when referencing the Azure resource names. They need to be correct for the detection code to work as expected!&lt;/p&gt;
&lt;p&gt;It&apos;s not quite as elegant as using the &lt;code&gt;import&lt;/code&gt; statement in your HCL code, but it does the job.&lt;/p&gt;
&lt;h2&gt;Example output&lt;/h2&gt;
&lt;p&gt;Here&apos;s an example of the output from the &lt;code&gt;terraform import&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data.azurerm_resource_group.group: Reading...
data.azurerm_client_config.client: Reading...
data.azurerm_client_config.client: Read complete after 0s [id=xxxxxxxxxxxx=]
data.azurerm_resource_group.group: Read complete after 0s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast]
azurerm_service_plan.plan: Importing from ID &quot;/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast&quot;...
azurerm_service_plan.plan: Import prepared!
  Prepared azurerm_service_plan for import
azurerm_service_plan.plan: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast]

Import successful!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the resultant plan shows no additional changes are necessary, which is just what we like to see!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;data.azurerm_resource_group.group: Reading...
data.azurerm_client_config.client: Reading...
data.azurerm_client_config.client: Read complete after 0s [id=xxxxxxxxxxxxx=]
data.azurerm_resource_group.group: Read complete after 0s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast]
azurerm_service_plan.plan: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast]
azurerm_linux_web_app.appservice: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/sites/appservice-tfupgrade-australiasoutheast]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if we apply this to an empty environment, it also runs successfully!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;azurerm_service_plan.plan: Creating...
azurerm_service_plan.plan: Still creating... [10s elapsed]
azurerm_service_plan.plan: Creation complete after 10s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast]
azurerm_linux_web_app.appservice: Creating...
azurerm_linux_web_app.appservice: Still creating... [10s elapsed]
azurerm_linux_web_app.appservice: Still creating... [20s elapsed]
azurerm_linux_web_app.appservice: Still creating... [30s elapsed]
azurerm_linux_web_app.appservice: Still creating... [40s elapsed]
azurerm_linux_web_app.appservice: Still creating... [50s elapsed]
azurerm_linux_web_app.appservice: Creation complete after 56s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/sites/appservice-tfupgrade-australiasoutheast]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Post-migration clean up&lt;/h2&gt;
&lt;p&gt;Whether you use the &lt;code&gt;import&lt;/code&gt; statement or the &lt;code&gt;terraform import&lt;/code&gt; command, once you&apos;ve migrated your Terraform resources in all existing environments, I recommend you remove the migration code from HCL and pipeline YAML files.&lt;/p&gt;
&lt;p&gt;Remove the &lt;code&gt;import&lt;/code&gt; statements as they have no further purpose, and you only risk encountering the issue mentioned above if you ever want to deploy to a new environment.&lt;/p&gt;
&lt;p&gt;There&apos;s less risk of leaving the conditional &lt;code&gt;terraform import&lt;/code&gt; commands in the pipeline YAML files, but as long as they remain they are slowing the pipeline down. Remove them to keep your pipelines running as fast as possible and to keep your YAML files as simple as possible. If you ever need to do another migration in the future, you&apos;ll have the commands in your source control history to refer to.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/terraform-logo.CiRDK2M7.png" width="309" height="312"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/terraform-logo.CiRDK2M7.png" width="309" height="312"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2023/12/migrate-terraform-resources</id>
    <updated>2023-12-04T08:00:00.000+10:30</updated>
    <title>Migrating deprecated Terraform resources</title>
    <link href="https://david.gardiner.net.au/2023/12/migrate-terraform-resources" rel="alternate" type="text/html" title="Migrating deprecated Terraform resources"/>
    <category term="Azure Pipelines"/>
    <category term="DevOps"/>
    <category term="Terraform"/>
    <published>2023-12-04T08:00:00.000+10:30</published>
    <summary type="html">Demonstrates a step-by-step approach for migrating deprecated Terraform resources to supported alternatives with minimal disruption.</summary>
    <content type="html">&lt;p&gt;One of the challenges with using Terraform for your infrastructure as code is that the providers (that interact with cloud providers like Azure) are updated very frequently, and especially with major version releases this includes deprecating specific resource types. For example, when the Azure provider (AzureRM) version 3.0 was released, it deprecated many resource types and data sources. Some of these still exist in version 3, but are &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/3.0-upgrade-guide#removal-of-deprecated-fields-data-sources-and-resources&quot;&gt;deprecated, will not receive any updates, and will be removed in version 4&lt;/a&gt;. Others have already been removed entirely.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/terraform-logo.CiRDK2M7_Z21UAf8.webp&quot; alt=&quot;Terraform logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I&apos;ve created an example repo that demonstrates the migration process outlined here. Find it at &lt;a href=&quot;https://github.com/flcdrg/terraform-azure-upgrade-resources&quot;&gt;https://github.com/flcdrg/terraform-azure-upgrade-resources&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While this post uses Azure and Azure Pipelines, the same principles should apply for other cloud providers and CI/CD systems.&lt;/p&gt;
&lt;p&gt;To set the scene, here&apos;s some Terraform code that creates an Azure App Service Plan and an App Service (&lt;a href=&quot;https://github.com/flcdrg/terraform-azure-upgrade-resources/blob/main/v2/app-service.tf&quot;&gt;src&lt;/a&gt;). The resource types are from v2.x AzureRM provider. Bear in mind, the last release of v2 was &lt;a href=&quot;https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v2.99.0&quot;&gt;v2.99.0&lt;/a&gt; in March 2022.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# https://registry.terraform.io/providers/hashicorp/azurerm/2.99.0/docs/resources/app_service_plan
resource &quot;azurerm_app_service_plan&quot; &quot;plan&quot; {
  name                = &quot;plan-tfupgrade-australiasoutheast&quot;
  resource_group_name = data.azurerm_resource_group.group.name
  location            = data.azurerm_resource_group.group.location
  kind                = &quot;Linux&quot;
  reserved            = true
  sku {
    tier = &quot;Basic&quot;
    size = &quot;B1&quot;
  }
}

# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service
resource &quot;azurerm_app_service&quot; &quot;appservice&quot; {
  app_service_plan_id = azurerm_app_service_plan.plan.id
  name                = &quot;appservice-tfupgrade-australiasoutheast&quot;
  location            = data.azurerm_resource_group.group.location
  resource_group_name = data.azurerm_resource_group.group.name
  https_only          = true

  app_settings = {
    &quot;TEST&quot; = &quot;TEST&quot;
  }

  site_config {
    always_on                 = true
    ftps_state                = &quot;Disabled&quot;
    http2_enabled             = true
    linux_fx_version          = &quot;DOTNETCORE|6.0&quot;
    min_tls_version           = &quot;1.2&quot;
    use_32_bit_worker_process = false
  }
  identity {
    type = &quot;SystemAssigned&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The documentation for AzureRM v3.x shows that these resource types are deprecated and will be completely removed in v4.x. In addition, as these resource types are not being updated, they don&apos;t support the latest features of Azure App Services, such as the new .NET 8 runtime.&lt;/p&gt;
&lt;p&gt;So how can we switch to the &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/service_plan&quot;&gt;azurerm_service_plan&lt;/a&gt; and &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app&quot;&gt;azurerm_linux_web_app&lt;/a&gt; resource types?&lt;/p&gt;
&lt;p&gt;You might think it&apos;s just a matter of just changing the resource types and updating a few properties. But if you tried that you&apos;ll discover that Terraform will try to delete the existing resources and create new ones. This is because the resource types are different, and Terraform doesn&apos;t know that they are actually the same thing because the state representation of those resources is different.&lt;/p&gt;
&lt;p&gt;Instead, we need to let Terraform know that the Azure resources that have already been created map to the new Terraform resource types we&apos;ve defined in our configuration. In addition, we want to do this in a testable way using a pull request to verify that our changes look correct before we merge them into the main branch.&lt;/p&gt;
&lt;p&gt;The approach we&apos;ll take is to make use of the relatively new &lt;a href=&quot;https://developer.hashicorp.com/terraform/language/import&quot;&gt;&lt;code&gt;import&lt;/code&gt; block&lt;/a&gt; language feature. (In a future blog post I&apos;ll cover when you might consider using the &lt;code&gt;terraform import&lt;/code&gt; CLI command instead).&lt;/p&gt;
&lt;p&gt;By using the &lt;code&gt;import&lt;/code&gt; block, we can tell Terraform that the existing resources in Azure should be mapped to the new resource types we&apos;ve defined in Terraform configuration. This means that Terraform will not try to delete the existing resources and create new ones. Instead, it will update the existing resources to match the Terraform configuration.&lt;/p&gt;
&lt;p&gt;In the following example, we&apos;re indicating that the Azure resource with the resource ID &lt;code&gt;/subscriptions/.../resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast&lt;/code&gt; should be mapped to the &lt;code&gt;azurerm_service_plan&lt;/code&gt; resource type. Note the use of the data block reference to insert the subscription ID, rather than hard-coding it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  id = &quot;/subscriptions/${data.azurerm_client_config.client.subscription_id}/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast&quot;
  to = azurerm_service_plan.plan
}

resource &quot;azurerm_service_plan&quot; &quot;plan&quot; {
  name                = &quot;plan-tfupgrade-australiasoutheast&quot;
  resource_group_name = data.azurerm_resource_group.group.name
  location            = data.azurerm_resource_group.group.location
  sku_name            = &quot;B1&quot;
  os_type             = &quot;Linux&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Often when you&apos;re adding the new resource, the property names and &apos;shape&apos; will change. Sometimes it&apos;s pretty easy to figure out the equivalent, but sometimes you might need some help. One option you can utilise is &lt;a href=&quot;https://developer.hashicorp.com/terraform/language/import/generating-configuration&quot;&gt;generating the configuration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this case, you add the &lt;code&gt;import&lt;/code&gt; block, but don&apos;t add the resource block. If you then run &lt;code&gt;terraform plan -generate-config-out=generated_resources.tf&lt;/code&gt;. Terraform will create a new file &lt;code&gt;generated_resources.tf&lt;/code&gt; which will contain generated resources. You can then copy/paste those over into your regular .tf files. You&apos;ll almost certainly want to edit them to remove redundant settings and replace hard-coded values with variable references where applicable. If you&apos;re doing this as part of a pipeline, publish the generated file as a build artifact, so you can download it and incorporate the changes. You could make this an optional part of the pipeline that is enabled by setting a pipeline parameter to true.&lt;/p&gt;
&lt;p&gt;There&apos;s still one problem to solve though. While we&apos;ve mapped the new resource types to the existing resources, Terraform state still knows about the old resource types, and will try to delete them now that they are no longer defined in the Terraform configuration. To solve this, we can use the &lt;a href=&quot;https://developer.hashicorp.com/terraform/cli/commands/state/rm&quot;&gt;&lt;code&gt;terraform state rm&lt;/code&gt;&lt;/a&gt; command to remove the old resources from state. If they&apos;re not in state, then Terraform doesn&apos;t know about them and won&apos;t try to delete them.&lt;/p&gt;
&lt;p&gt;The following script will remove the old resources from state if they exist. Note that the &lt;code&gt;terraform state rm&lt;/code&gt; command will fail if the resource doesn&apos;t exist in state, so we need to check for the existence of the resource first.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Remove state of old resources from Terraform
mapfile -t RESOURCES &amp;lt; &amp;lt;( terraform state list )

if [[ &quot; ${RESOURCES[@]} &quot; =~ &quot;azurerm_app_service_plan.plan&quot; ]]; then
  terraform state rm azurerm_app_service_plan.plan
fi

if [[ &quot; ${RESOURCES[@]} &quot; =~ &quot;azurerm_app_service.appservice&quot; ]]; then
  terraform state rm azurerm_app_service.appservice
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need to add an entry in this script for each Terraform resource type that you are removing.&lt;/p&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;Ok, so we have a strategy for upgrading our Terraform resources. But how do we test it? We don&apos;t want to just merge the changes into the main branch and hope for the best. We want to test it first, and do this in isolation from other changes that might be happening in the main branch (or other branches). To test our changes need to update the Terraform state. But if we update the state used by everyone else then we won&apos;t be popular when their builds start failing because Terraform will be trying to recreate resources that we&apos;ve just deleted from state. Except those resources still exist in Azure!&lt;/p&gt;
&lt;p&gt;What we want is a local copy of the Terraform state that we can try out our changes in without affecting anyone else. One way to do this is to copy the remote state to a local file, then reinitialise Terraform to use the &apos;local&apos; backend. Obviously we won&apos;t do a real deployment using this, but it is perfect for running &lt;code&gt;terraform plan&lt;/code&gt; against.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example script that will copy the remote state to a local file, then reinitialise Terraform to use the local backend. It assumes that your backend configuration is defined separately in a &lt;a href=&quot;https://github.com/flcdrg/terraform-azure-upgrade-resources/blob/main/v3/backend.tf&quot;&gt;&lt;code&gt;backend.tf&lt;/code&gt; file&lt;/a&gt;. Normally this would be pointing to a remote backend (e.g. Terraform Cloud or an Azure Storage account), within the pipeline run we replace this file with configuration to use a local backend.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;terraform state pull &amp;gt; $(Build.ArtifactStagingDirectory)/pull.tfstate

cat &amp;gt; backend.tf &amp;lt;&amp;lt;EOF
terraform {
  backend &quot;local&quot; {
    path = &quot;$(Build.ArtifactStagingDirectory)/pull.tfstate&quot;
  }
}
EOF

# Reset Terraform to use local backend
terraform init -reconfigure -no-color -input=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now should have all the pieces in place to test our changes on PR build, and then once we&apos;re happy with the plan, merge the changes and run the migration for real. If you have multiple environments (dev/test/prod) then you can roll this out to each environment as part of the normal release process.&lt;/p&gt;
&lt;p&gt;For Azure Pipelines, we make use of conditional expressions, so that on PR builds we test the migration using local state, but on the main branch we modify the remote state and actually apply the changes.&lt;/p&gt;
&lt;p&gt;Here&apos;s the Azure Pipeline in full (&lt;a href=&quot;https://github.com/flcdrg/terraform-azure-upgrade-resources/blob/main/upgrade.yml&quot;&gt;src&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;trigger: none

pr:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  - group: Terraform-Token

jobs:
  - job: build
    displayName: &quot;Test Terraform Upgrade&quot;

    variables:
      TerraformSourceDirectory: $(System.DefaultWorkingDirectory)/v3

    steps:
      - script: echo &quot;##vso[task.setvariable variable=TF_TOKEN_app_terraform_io]$(TF_TOKEN)&quot;
        displayName: &quot;Terraform Token&quot;

      - task: TerraformInstaller@2
        displayName: &quot;Terraform: Installer&quot;
        inputs:
          terraformVersion: &quot;latest&quot;

      - task: TerraformCLI@2
        displayName: &quot;Terraform: init&quot;
        inputs:
          command: init
          workingDirectory: &quot;$(TerraformSourceDirectory)&quot;
          backendType: selfConfigured
          commandOptions: -no-color -input=false
          allowTelemetryCollection: false

      - ${{ if ne(variables[&apos;Build.SourceBranch&apos;], &apos;refs/heads/main&apos;) }}:
          # Copy state from Terraform Cloud to local, so we can modify it without affecting the remote state
          - script: |
              terraform state pull &amp;gt; $(Build.ArtifactStagingDirectory)/pull.tfstate

              # Write multiple lines of text to local file using bash
              cat &amp;gt; backend.tf &amp;lt;&amp;lt;EOF
              terraform {
                backend &quot;local&quot; {
                  path = &quot;$(Build.ArtifactStagingDirectory)/pull.tfstate&quot;
                }
              }
              EOF

              # Reset Terraform to use local backend
              terraform init -reconfigure -no-color -input=false
            displayName: &quot;Script: Use Terraform Local Backend&quot;
            workingDirectory: $(TerraformSourceDirectory)

      - script: |
          # Remove state of old resources from Terraform
          mapfile -t RESOURCES &amp;lt; &amp;lt;( terraform state list )

          if [[ &quot; ${RESOURCES[@]} &quot; =~ &quot;azurerm_app_service_plan.plan&quot; ]]; then
            terraform state rm azurerm_app_service_plan.plan
          fi

          if [[ &quot; ${RESOURCES[@]} &quot; =~ &quot;azurerm_app_service.appservice&quot; ]]; then
            terraform state rm azurerm_app_service.appservice
          fi
        displayName: &quot;Script: Remove old resources from Terraform State&quot;
        workingDirectory: $(TerraformSourceDirectory)

      - task: TerraformCLI@2
        displayName: &quot;Terraform: validate&quot;
        inputs:
          command: validate
          workingDirectory: &quot;$(TerraformSourceDirectory)&quot;
          commandOptions: -no-color

      - ${{ if ne(variables[&apos;Build.SourceBranch&apos;], &apos;refs/heads/main&apos;) }}:
          - task: TerraformCLI@2
            displayName: &quot;Terraform: plan&quot;
            inputs:
              command: plan
              workingDirectory: &quot;$(TerraformSourceDirectory)&quot;
              commandOptions: -no-color -input=false -detailed-exitcode
              environmentServiceName: Azure MSDN - rg-tfupgrade-australiasoutheast
              publishPlanResults: Plan
              allowTelemetryCollection: false

      - ${{ if eq(variables[&apos;Build.SourceBranch&apos;], &apos;refs/heads/main&apos;) }}:
          - task: TerraformCLI@2
            displayName: &quot;Terraform: apply&quot;
            inputs:
              command: apply
              workingDirectory: &quot;$(TerraformSourceDirectory)&quot;
              commandOptions: -no-color -input=false -auto-approve
              allowTelemetryCollection: false
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Using it in practise&lt;/h2&gt;
&lt;p&gt;Ideally when you migrate the resource types, there will be no changes to the properties (and Terraform will report that no changes need to be made). Often the new resource type provides additional properties that you can take advantage of. Whether you set this initially or in a subsequent PR is up to you.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example output from terraform plan:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Terraform v1.6.4
on linux_amd64
Initializing plugins and modules...
data.azurerm_resource_group.group: Refreshing...
data.azurerm_client_config.client: Refreshing...
data.azurerm_client_config.client: Refresh complete after 0s [id=xxxxxxxxxxx=]
data.azurerm_resource_group.group: Refresh complete after 0s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast]
azurerm_service_plan.plan: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast]
azurerm_linux_web_app.appservice: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/sites/appservice-tfupgrade-australiasoutheast]

Terraform will perform the following actions:

  # azurerm_linux_web_app.appservice will be imported
    resource &quot;azurerm_linux_web_app&quot; &quot;appservice&quot; {
        app_settings                                   = {
            &quot;TEST&quot; = &quot;TEST&quot;
        }
        client_affinity_enabled                        = false
        client_certificate_enabled                     = false
        client_certificate_mode                        = &quot;Required&quot;
        custom_domain_verification_id                  = (sensitive value)
        default_hostname                               = &quot;appservice-tfupgrade-australiasoutheast.azurewebsites.net&quot;
        enabled                                        = true
        ftp_publish_basic_authentication_enabled       = true
        https_only                                     = true
        id                                             = &quot;/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/sites/appservice-tfupgrade-australiasoutheast&quot;
        key_vault_reference_identity_id                = &quot;SystemAssigned&quot;
        kind                                           = &quot;app,linux&quot;
        location                                       = &quot;australiasoutheast&quot;
        name                                           = &quot;appservice-tfupgrade-australiasoutheast&quot;
        outbound_ip_address_list                       = [
            &quot;52.189.223.107&quot;,
            &quot;13.77.42.25&quot;,
            &quot;13.77.46.217&quot;,
            &quot;52.189.221.141&quot;,
            &quot;13.77.50.99&quot;,
        ]
        outbound_ip_addresses                          = &quot;52.189.223.107,13.77.42.25,13.77.46.217,52.189.221.141,13.77.50.99&quot;
        possible_outbound_ip_address_list              = [
            &quot;52.189.223.107&quot;,
            &quot;13.77.42.25&quot;,
            &quot;13.77.46.217&quot;,
            &quot;52.189.221.141&quot;,
            &quot;52.243.85.201&quot;,
            &quot;52.243.85.94&quot;,
            &quot;52.189.234.152&quot;,
            &quot;13.77.56.61&quot;,
            &quot;52.189.214.112&quot;,
            &quot;20.11.210.198&quot;,
            &quot;20.211.233.197&quot;,
            &quot;20.211.238.191&quot;,
            &quot;20.11.210.187&quot;,
            &quot;20.11.211.1&quot;,
            &quot;20.11.211.80&quot;,
            &quot;4.198.70.38&quot;,
            &quot;20.92.41.250&quot;,
            &quot;4.198.68.27&quot;,
            &quot;4.198.68.42&quot;,
            &quot;20.92.47.59&quot;,
            &quot;20.92.42.78&quot;,
            &quot;13.77.50.99&quot;,
        ]
        possible_outbound_ip_addresses                 = &quot;52.189.223.107,13.77.42.25,13.77.46.217,52.189.221.141,52.243.85.201,52.243.85.94,52.189.234.152,13.77.56.61,52.189.214.112,20.11.210.198,20.211.233.197,20.211.238.191,20.11.210.187,20.11.211.1,20.11.211.80,4.198.70.38,20.92.41.250,4.198.68.27,4.198.68.42,20.92.47.59,20.92.42.78,13.77.50.99&quot;
        public_network_access_enabled                  = true
        resource_group_name                            = &quot;rg-tfupgrade-australiasoutheast&quot;
        service_plan_id                                = &quot;/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast&quot;
        site_credential                                = (sensitive value)
        tags                                           = {}
        webdeploy_publish_basic_authentication_enabled = true

        identity {
            identity_ids = []
            principal_id = &quot;a71e1fd5-e61b-4591-a439-98bad90cc837&quot;
            tenant_id    = &quot;59b0934d-4f35-4bff-a2b7-a451fe5f8bd6&quot;
            type         = &quot;SystemAssigned&quot;
        }

        site_config {
            always_on                               = true
            auto_heal_enabled                       = false
            container_registry_use_managed_identity = false
            default_documents                       = []
            detailed_error_logging_enabled          = false
            ftps_state                              = &quot;Disabled&quot;
            health_check_eviction_time_in_min       = 0
            http2_enabled                           = true
            linux_fx_version                        = &quot;DOTNETCORE|6.0&quot;
            load_balancing_mode                     = &quot;LeastRequests&quot;
            local_mysql_enabled                     = false
            managed_pipeline_mode                   = &quot;Integrated&quot;
            minimum_tls_version                     = &quot;1.2&quot;
            remote_debugging_enabled                = false
            remote_debugging_version                = &quot;VS2019&quot;
            scm_minimum_tls_version                 = &quot;1.2&quot;
            scm_type                                = &quot;VSTSRM&quot;
            scm_use_main_ip_restriction             = false
            use_32_bit_worker                       = false
            vnet_route_all_enabled                  = false
            websockets_enabled                      = false
            worker_count                            = 1

            application_stack {
                docker_registry_password = (sensitive value)
                dotnet_version           = &quot;6.0&quot;
            }
        }
    }

  # azurerm_service_plan.plan will be imported
    resource &quot;azurerm_service_plan&quot; &quot;plan&quot; {
        id                           = &quot;/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-tfupgrade-australiasoutheast/providers/Microsoft.Web/serverfarms/plan-tfupgrade-australiasoutheast&quot;
        kind                         = &quot;linux&quot;
        location                     = &quot;australiasoutheast&quot;
        maximum_elastic_worker_count = 1
        name                         = &quot;plan-tfupgrade-australiasoutheast&quot;
        os_type                      = &quot;Linux&quot;
        per_site_scaling_enabled     = false
        reserved                     = true
        resource_group_name          = &quot;rg-tfupgrade-australiasoutheast&quot;
        sku_name                     = &quot;B1&quot;
        tags                         = {}
        worker_count                 = 1
        zone_balancing_enabled       = false
    }

Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the next post, I&apos;ll cover a few things to watch out for, and some post-migration clean up steps.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/terraform-logo.CiRDK2M7.png" width="309" height="312"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/terraform-logo.CiRDK2M7.png" width="309" height="312"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2023/11/terraform-init-error</id>
    <updated>2023-11-27T09:00:00.000+10:30</updated>
    <title>Terraform command &apos;init&apos; failed with exit code &apos;1&apos;</title>
    <link href="https://david.gardiner.net.au/2023/11/terraform-init-error" rel="alternate" type="text/html" title="Terraform command &apos;init&apos; failed with exit code &apos;1&apos;"/>
    <category term="Azure Pipelines"/>
    <category term="DevOps"/>
    <category term="Terraform"/>
    <published>2023-11-27T09:00:00.000+10:30</published>
    <summary type="html">Walks through diagnosing a Terraform init failure in Azure Pipelines and the fix needed to get the workflow running.</summary>
    <content type="html">&lt;p&gt;I&apos;m setting up a new GitHub repo to demonstrate using Terraform with Azure Pipelines via the CLI and I hit a weird error right at the start. I was using &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=JasonBJohnson.azure-pipelines-tasks-terraform&quot;&gt;Jason Johnson&apos;s Azure Pipelines Terraform Tasks&lt;/a&gt; extension like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- task: TerraformCLI@2
  inputs:
    command: init
    workingDirectory: &quot;$(System.DefaultWorkingDirectory)/v2&quot;
    backendType: selfConfigured
    commandOptions: -no-color -input=false
    allowTelemetryCollection: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it kept failing with the error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/opt/hostedtoolcache/terraform/1.6.4/x64/terraform version
Terraform v1.6.4
on linux_amd64
+ provider registry.terraform.io/hashicorp/azurerm v2.99.0
+ provider registry.terraform.io/hashicorp/random v3.5.1
/opt/hostedtoolcache/terraform/1.6.4/x64/terraform init --input=false --no-color
Usage: terraform [global options] init [options]
...
Terraform command &apos;init&apos; failed with exit code &apos;1&apos;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tried all sorts of things. It looked identical to other pipelines I had working (and ones I&apos;d seen online). Was there a weird invisible character being passed on the command line? Out of desperation I copied the &lt;code&gt;commandOptions&lt;/code&gt; line from another pipeline (which looked identical except that the arguments were in a different order).&lt;/p&gt;
&lt;p&gt;But when I looked at the diff, not only were the arguments different, I realised that the dashes were different too! Terraform CLI arguments use a single dash, not a double dash. So the correct line is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;commandOptions: -no-color -input=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In hindsight, I realised that this is the first pipeline I&apos;ve written on GitHub using the Terraform CLI (e.g. not in a work context) and so I did it manually, rather than copy/pasting from an existing (working) pipeline. A pity Terraform doesn&apos;t support double dashes, but there you go.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/terraform-logo.CiRDK2M7.png" width="309" height="312"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/terraform-logo.CiRDK2M7.png" width="309" height="312"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2022/02/passed-az400</id>
    <updated>2022-02-23T19:30:00.000+10:30</updated>
    <title>Passed AZ-400</title>
    <link href="https://david.gardiner.net.au/2022/02/passed-az400" rel="alternate" type="text/html" title="Passed AZ-400"/>
    <category term="Azure"/>
    <category term="Azure DevOps"/>
    <category term="DevOps"/>
    <category term="GitHub"/>
    <category term="Training and Certification"/>
    <published>2022-02-23T19:30:00.000+10:30</published>
    <summary type="html">I&apos;m pleased to report that today I passed Microsoft exam AZ-400: Designing and Implementing Microsoft DevOps Solutions, which combined with AZ-201 that I took last year, now qualifies me for the Microsoft Certified.</summary>
    <content type="html">&lt;p&gt;I&apos;m pleased to report that today I passed Microsoft exam &lt;a href=&quot;https://learn.microsoft.com/credentials/certifications/exams/az-400/?WT.mc_id=DOP-MVP-5001655&quot;&gt;AZ-400: Designing and Implementing Microsoft DevOps Solutions&lt;/a&gt;, which combined with &lt;a href=&quot;/2021/07/passed-az-204&quot;&gt;AZ-201 that I took last year&lt;/a&gt;, now qualifies me for the &lt;a href=&quot;https://learn.microsoft.com/credentials/certifications/devops-engineer/?WT.mc_id=DOP-MVP-5001655&quot;&gt;Microsoft Certified: DevOps Engineer Expert certification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.credly.com/badges/6f929582-8328-48d1-b65b-0dcd99fb7cd8/public_url&quot;&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/microsoft-certified-devops-engineer-expert.DB_fO9io_2upomc.webp&quot; alt=&quot;Microsoft Certified: DevOps Engineer Expert badge&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.credly.com/badges/6f929582-8328-48d1-b65b-0dcd99fb7cd8/public_url&quot;&gt;View my verified achievement from Microsoft&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The exam is quite broad in the content it covers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Develop an instrumentation strategy (5-10%)&lt;/li&gt;
&lt;li&gt;Develop a Site Reliability Engineering (SRE) strategy (5-10%)&lt;/li&gt;
&lt;li&gt;Develop a security and compliance plan (10-15%)&lt;/li&gt;
&lt;li&gt;Manage source control (10-15%)&lt;/li&gt;
&lt;li&gt;Facilitate communication and collaboration (10-15%)&lt;/li&gt;
&lt;li&gt;Define and implement continuous integration (20-25%)&lt;/li&gt;
&lt;li&gt;Define and implement a continuous delivery and release management strategy (10-15%)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some areas I&apos;d been working with for quite a few years, but others were new to me. To help prepare I used a couple of resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/learn/certifications/exams/az-400?WT.mc_id=DOP-MVP-5001655#two-ways-to-prepare&quot;&gt;Microsoft Learn content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20230522220903/https://www.pluralsight.com/paths/designing-and-implementing-microsoft-devops-solutions-az-400&quot;&gt;Pluralsight certification prep path&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nice to get that one dusted.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/microsoft-certified-devops-engineer-expert.DB_fO9io.png" width="300" height="300"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/microsoft-certified-devops-engineer-expert.DB_fO9io.png" width="300" height="300"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2019/06/teamcity-conditional-build-configurations</id>
    <updated>2019-06-21T13:00:00.000+09:30</updated>
    <title>TeamCity conditional build configurations - master vs other branches</title>
    <link href="https://david.gardiner.net.au/2019/06/teamcity-conditional-build-configurations" rel="alternate" type="text/html" title="TeamCity conditional build configurations - master vs other branches"/>
    <category term="DevOps"/>
    <category term="TeamCity"/>
    <category term="Builds"/>
    <published>2019-06-21T13:00:00.000+09:30</published>
    <summary type="html">TeamCity doesn&apos;t currently support conditional build steps (Vote on this issue).</summary>
    <content type="html">&lt;p&gt;TeamCity doesn&apos;t currently support conditional build steps (Vote on &lt;a href=&quot;https://youtrack.jetbrains.com/issue/TW-17939&quot;&gt;this issue&lt;/a&gt;). So how can you have different steps for a master builds vs. other branches?&lt;/p&gt;
&lt;p&gt;This is not an uncommon scenario - you might want to perform similar build steps for all builds, but for a master build there may be some extra steps (eg. publishing NuGet/NPM packages to a repository, triggering an external activity) that you don&apos;t want to run for a branch build (eg. a build for a pull request).&lt;/p&gt;
&lt;p&gt;It is possible to work around this limitation by creating multiple build configurations - one for master and another for non-master branches. But how can you do that efficiently? One approach is to enable &lt;a href=&quot;https://www.jetbrains.com/help/teamcity/storing-project-settings-in-version-control.html&quot;&gt;Versioned Settings&lt;/a&gt; using Kotlin.&lt;/p&gt;
&lt;p&gt;Kotlin is a language created by JetBrains that targets the JVM (and is now the &lt;a href=&quot;https://developer.android.com/kotlin&quot;&gt;recommended language for Android&lt;/a&gt; development). But it&apos;s also one of the language choices when you enable Versioned Settings in TeamCity. (The other is XML, which will look familiar if you&apos;ve ever played with TeamCity&apos;s meta-runners).&lt;/p&gt;
&lt;p&gt;I&apos;m not a Kotlin expert, but I managed to figure out that you can use it to generate the different variations, such that when TeamCity parses the Kotlin, it ends up creating multiple build configurations.&lt;/p&gt;
&lt;p&gt;One thing to be aware of is that TeamCity parses the Kotlin up-front to create the build configurations. It doesn&apos;t evaluate it as the build runs (so it &lt;a href=&quot;https://web.archive.org/web/20250219121517/https://teamcity-support.jetbrains.com/hc/en-us/community/posts/360001222619-Conditional-step-with-kotlin-dsl&quot;&gt;isn&apos;t possible to have an expression that tests an environment variable or parameter&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The following example shows generating two build configurations - &quot;Build_CI_Master&quot; and &quot;Build_CI_Branches&quot;. The difference is that the Master build configuration enables package indexing. The build configurations use different branch filters to control whether they apply to master or non-master branches.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2019/06/global-devops-bootcamp</id>
    <updated>2019-06-16T15:00:00.000+09:30</updated>
    <title>Global DevOps Bootcamp 2019</title>
    <link href="https://david.gardiner.net.au/2019/06/global-devops-bootcamp" rel="alternate" type="text/html" title="Global DevOps Bootcamp 2019"/>
    <category term="Events"/>
    <category term="DevOps"/>
    <category term="Talks"/>
    <published>2019-06-16T15:00:00.000+09:30</published>
    <summary type="html">Yesterday (Saturday 15th) was the 3rd Global DevOps Bootcamp we&apos;ve run in Adelaide.</summary>
    <content type="html">
&lt;p&gt;Yesterday (Saturday 15th) was the 3rd &lt;a href=&quot;https://globaldevopsbootcamp.com/&quot;&gt;Global DevOps Bootcamp&lt;/a&gt; we&apos;ve run in Adelaide.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/gdbc-selfie._NQ9PG2O_1669qd.webp&quot; alt=&quot;Selfie&quot; /&gt;
Using the great facilities of the University of South Australia, a bunch of people gathered to learning more about DevOps, and in particular this year on the &apos;Run&apos; part of DevOps, including introducing the role of a Site Reliability Engineer (SRE).&lt;/p&gt;
&lt;p&gt;The day started off with a recorded introduction from international event organisers, then a keynote from &lt;a href=&quot;https://twitter.com/niallm&quot;&gt;Niall Murphy&lt;/a&gt; (Microsoft Ireland Director of Engineering for Azure Cloud Services and Site Reliability Engineering), followed by a local keynote delivered by me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/attendees-watching-keynote.DGYmlYws_Z23pWnX.webp&quot; alt=&quot;Keynote&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After this people got into teams and worked through different challenges relating to a mythical online car-parts company that was experiencing all kinds of problems in production. Teams were encouraged to follow the pattern of &apos;Detect, Respond and Recover&apos; - identifying the problem, putting in a quick fix to get the website back up and then implementing a long-term solution to prevent the problem from reoccurring.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/gdbc-working-on-challenges.yFgMV_XQ_OXUiK.webp&quot; alt=&quot;Team challenges&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The teams worked through the rest of the day, doing a great job on the challenges. The light-hearted video introductions to each challenge were a new thing this year and were a nice touch.&lt;/p&gt;
&lt;p&gt;All credit to Rene, Marcel, Mathias and their team of helpers who put all the content together and coordinated a massive global event together with Microsoft.&lt;/p&gt;
&lt;p&gt;Microsoft provided Subway for lunch and special thanks to my employer &lt;a href=&quot;https://www.rldatix.com&quot;&gt;RLDatix&lt;/a&gt; who picked up the tab for fresh coffees for all attendees.&lt;/p&gt;
</content>
  </entry>
</feed>
