Skip to content

2021๐Ÿ”—

Go R1 Day 37

progress

  • initialized new start of cli tool to try to parse markdown content using Goldmark1 and then convert markdown links into reference style links
  • Improved default template for starting new basic cli tools with Go to include zerolog and run based design instead of the majority of logic being in main.

Go R1 Day 35

progress

  • Worked with Taskflow a bit more.
  • Need to identify better error handling pattern on when to resolve vs handle internal to a function, as it feels like I'm doing needless error checking.
  • Wrote func to run terraform init, plan, and apply.
  • This takes dynamical inputs for vars and backend file.
  • Also dynamically switches terraform versions by running tfswitch.

Definitely more verbose code than powershell, but it's a good way to get used to Go while achieving some useful automation tasks I need to do.

Example of some code for checking terraform path.

func terraformPath(tf *taskflow.TF) (terraformPath string, err error) {
    terraformPath = path.Join(toolsDir, "terraform")
    if _, err := os.Stat(terraformPath); os.IsNotExist(err) {
        tf.Errorf("โ— cannot find terraform at: [%s] -> [%v]", terraformPath, err)
        return "", err
    }
    tf.Logf("โœ… found terraform at: [%s]", terraformPath)
    return terraformPath, nil
}
terraformPath, err := terraformPath(tf)
if err != nil {
  tf.Errorf("โ— unable to proceed due to not finding terraform installed [%v]", err)
  return
}

However, once I call this, I'm see more effort in handling, which feels like I'm double double work at times.

Go R1 Day 34

progress

  • figured out how to import util/logger.go as a package
  • after much confusion due to logger, log, *zerolog.Logger and more variables all deviously similar in name... how to pass around the initialized package logger that I configure.
  • learned that global scope and package scoped loggers being initialized at run is concerned an anti-pattern
  • properly wrapping to avoid the log initialization on import with type Logger struct { logger: *zerolog.Logger; } as an example avoids the same behavior as: var Log *zerolog.Logger
  • will evaluate better scoping in the future, but for now figured it would be a ๐Ÿš€ #shipit moment to improve as I can later. 1

Go R1 Day 33

progress

  • successfully created logging package using zerolog
  • learned about scoping with packages
  • linked to a private internal repository and how to leverage the module replace operator to temporarily alter path import from url to local override.
  • middleware is a newer concept, so I need to learn more on this later so I can understand how to use to inject special log handling for http requests and other actions.

Thoughts for today are that the pressure of jumping into an existing codebase is resulting in me moving faster than I probably should. I'm going to take some time to keep doing the web fundamentals, lambda, and exercisms to ensure I'm setting a better foundation long-term, and not just winging it. ๐Ÿ˜„

Go R1 Day 32

progress

  • created some structured logging improvements with zerolog
  • began exploration of middleware concepts for logging
  • generated test stubs using gotests

Go R1 Day 31

progress

  • Learned a bit about idiomatic patterns wtih error handling.
  • Learned about inline block intiailization of variables using if err := method(); err != nil {...} approach.
  • Considered a bit more idiomatic patterns when I noticed excessibe nested if blocks.
tfdir := tf.Params().String("tfdir")
if tfdir != "" {
  tf.Logf("tfdir set to: [%s]", tfdir)
} else {
  tf.Errorf("๐Ÿงช failed to get tfdir parameter: [%v]", tfdir)
}

This would probably be more in alignment with Go standards by writing as:

tfdir := tf.Params().String("tfdir")
if tfdir == "" {
  tf.Errorf("๐Ÿงช failed to get tfdir parameter: [%v]", tfdir)
  return
}
tf.Logf("tfdir set to: [%s]", tfdir)

This reduces the noise and keeps things pretty flat.

When Should I Use One Liner if...else Statements in Go?)

Go R1 Day 30

progress

  • Built some go functions for build tasks work with terraform and setup of projects using taskflow.

Learned one one to pass in arguments using slices. I'm pretty sure you can use some stringbuilder type functionality to get similar behavior, but this worked fine for my use case.

cmdParams := []string{}
cmdParams = append(cmdParams, "-chdir="+tfdir)
cmdParams = append(cmdParams, "init")
cmdParams = append(cmdParams, "-input=false")
cmdParams = append(cmdParams, "-backend=true")
cmdParams = append(cmdParams, "-backend-config="+tfconfig)
terraformCmd := tf.Cmd(terraformPath, cmdParams...)
if err := terraformCmd.Run(); err != nil {
  tf.Errorf("โญ• terraform init failed: [%v]", err)
  return
}

Go R1 Day 29

progress

  • Evaluated Mage as a replacement for bash/pwsh based tasks for automation with Azure Pipelines.
  • Was able to get terraform to run with dynamic configuration using the following approach:

Install with

go get -u github.com/magefile/mage/mg
go mod init mage-build
go get github.com/magefile/mage/mg
go get github.com/magefile/mage/sh
go mod tidy

Then to get mage-select run:

GO111MODULE=off go get github.com/iwittkau/mage-select
cd $GOPATH/src/github.com/iwittkau/mage-select
mage install

Configure some constants, which I'd probably do differently later. For now, this is a good rough start.

const (
    repo          = "myrepopath"
    name          = "myreponame"
    buildImage    = "mcr.microsoft.com/vscode/devcontainers/base:0-focal"
    terraformDir  = "terraform/stack"
    config_import = "qa.config"
)
func TerraformInit() error {
    params := []string{"-chdir=" + terraformDir}
    params = append(params, "init")
    params = append(params, "-input=false")
    params = append(params, "-var", "config_import="+config_import+".yml")

    // Backend location configuration only changes during the init phase, so you do not need to provide this to each command thereafter
    // https://github.com/hashicorp/terraform/pull/20428#issuecomment-470674564
    params = append(params, "-backend-config=./"+config_import+".tfvars")
    fmt.Println("starting terraform init")
    err := sh.RunV("terraform", params...)
    if err != nil {
        return err
    }
    return nil
}

Once terraform was initialized, it could be planned.

func TerraformPlan() error {
    mg.Deps(TerraformInit)
    params := []string{"-chdir=" + terraformDir}
    params = append(params, "plan")
    params = append(params, "-input=false")
    params = append(params, "-var", "config_import="+config_import+".yml")
    fmt.Println("starting terraform plan")
    err := sh.RunV("terraform", params...)
    if err != nil {
        return err
    }
    return nil
}
  • Of interest as well was mage-select, providing a new gui option for easier running by others joining a project.

mages-select-on-console

Fix Terraform Provider Path in State

Fixing Terraform provider paths in state might be required after upgrading to 0.13-0.14 if your prior state has the following paths.

First, get the terraform providers from state using: terraform providers

The output should look similar to this:

image-of-providers

To fix these, try running the commands to fix state. Please adjust to the required providers your state uses, and make sure your tooling has a backup of the state file in case something goes wrong. Terraform Cloud should have this backed up automatically if it's your backend.

terraform state replace-provider -- registry.terraform.io/-/aws registry.terraform.io/hashicorp/aws
terraform state replace-provider -- registry.terraform.io/-/random registry.terraform.io/hashicorp/random
terraform state replace-provider -- registry.terraform.io/-/null registry.terraform.io/hashicorp/null
terraform state replace-provider -- registry.terraform.io/-/azuredevops registry.terraform.io/microsoft/azuredevops

The resulting changes can be seen when running terraform providers and seeing the dash is now gone.

image-of-providers-changed

Upgrading to Terraform v0.13 - Terraform by HashiCorp

{{< admonition type="Example" title="Loop" open="false">}}

If you have multiple workspaces in the same folder, you'll have to run fix on their seperate state files.

This is an example of a quick adhoc loop with PowerShell to make this a bit quicker, using tfswitch cli tool.

tf workspace list | ForEach-Object {
    $workspace = $_.Replace('*','').Trim()
    Write-Build Green "Selecting workspace: $workspace"
    tf workspace select $workspace
    tfswitch 0.13.5
    tf 013.upgrade
    tfswitch
    tf init
    # Only use autoapprove once you are confident of these changes
    terraform state replace-provider -auto-approve -- registry.terraform.io/-/aws registry.terraform.io/hashicorp/aws
    terraform state replace-provider -auto-approve -- registry.terraform.io/-/random registry.terraform.io/hashicorp/random
    terraform state replace-provider -auto-approve -- registry.terraform.io/-/null registry.terraform.io/hashicorp/null
    terraform state replace-provider -auto-approve -- registry.terraform.io/-/azuredevops registry.terraform.io/microsoft/azuredevops
    tf validate
}

{{< /admonition >}}