Skip to content

posts๐Ÿ”—

Go R1 Day 68

progress

  • Did exercism.io for gigasecond puzzle.
package gigasecond

// import path for the time package from the standard library
import (
    "time"
)

// gigasecond represents a very very very small portion of a second.
const gigasecond = 1000000000

// AddGigasecond adds a very very very small portion of a second called a gigasecond to a provided time input.
func AddGigasecond(t time.Time) time.Time {
    gcDuration := gigasecond * time.Second
    n := t.Add(gcDuration)
    return n
}
  • Learned a bit more about using Math.Pow(), conversion of floats/ints, and dealing with time.Duration.
  • Tried using Math.Pow() to work through the issue, but got mixed up when using time.Duration() which expects nanoseconds, and such. Went ahead and just used a constant for the exercise as not likely to use gigaseconds anytime soon. ๐Ÿ˜€

Go R1 Day 67

progress

Built functionality in my blog repo to create a new 100DaysOfCode post using Mage. This provides an interactive prompt that automatically tracks the days left and increments the counter as this progresses.

  • ingest toml configuration
  • unmarshal to struct
  • update struct
  • marshal and write back to the toml configuration file
  • replace matched tokens in file

Go R1 Day 66

progress

This wasn't specific to Go, but was the first step towards using Go in a distributed test.

Dapr

I had an interesting project today with my first development level effort using Kubernetes. Here's my log of attempting to use Getting started with Dapr | Dapr Docs and getting two Go APIs to talk to each other with it.

First, what is Dapr?

Dapr is a portable, event-driven runtime that makes it easy for any developer to build resilient, stateless and stateful applications that run on the cloud and edge and embraces the diversity of languages and developer frameworks. 1 ... Dapr codifies the best practices for building microservice applications into open, independent building blocks that enable you to build portable applications with the language and framework of your choice. Each building block is completely independent and you can use one, some, or all of them in your application.

From this, it sounds like Dapr helps solve issues by abstracting the "building blocks" away from the business logic. Rather than focusing on the implementation level concern of how to talk from service to service, Dapr can help with this.

Instead of relying on provider specific key-value store, such as AWS SSM Parameter store, Dapr abstracts that too.

It's interesting as this concept of abstraction on a service level is something new to me. Good abstractions in software are hard but critical to maintainability long-term. Provider-level abstractions are something on an entirely different scale.

Setup

  • Enable Kubernetes on Docker Desktop.
  • Install Lens: brew install lens
  • Pop this open and Cmd+, to get to settings.
  • Add dapr helm charts: https://dapr.github.io/helm-charts/
  • Connect to local single-node Kubernetes cluster and open the charts section in Lens.
  • Install Dapr charts.
  • Celebrate your master of all things Kubernetes.

Master Of Kubernetes

I think I'll achieve the next level when I don't do this in Lens. I'll have to eventually use some cli magic to deploy my changes via helm or level-up to Pulumi. ๐Ÿ˜€ Until then, I'll count myself as victorious.

A Practical Test

Go R1 Day 65

progress

  • Built mage tasks for go formatting and linting.

Using this approach, you can now drop a magefile.go file into a project and set the following:

// +build mage

package main

import (

    "github.com/magefile/mage/mg"
    "github.com/pterm/pterm"

    // mage:import
    "github.com/sheldonhull/magetools/gotools"
)

Calling this can be done directly now as part of a startup task.

// Init runs multiple tasks to initialize all the requirements for running a project for a new contributor.
func Init() error {
    fancy.IntroScreen(ci.IsCI())
    pterm.Success.Println("running Init()...")
    mg.Deps(Clean, createDirectories)
    if err := (gotools.Golang{}.Init()); err != nil {  // <----- From another package.
        return err
    }

    return nil
}

Additionally, handled some Windows executable path issues by making sure to wrap up the path resolution.

// if windows detected, add the exe to the binary path
var extension string
if runtime.GOOS == "windows" {
  extension = ".exe"
}
toolPath := filepath.Join("_tools", item+extension)

First Pass With Pulumi

Why

Instead of learning a new domain specific language that wraps up cloud provider API's, this let's the developer use their preferred programming language, while solving several problems that using the API's directly don't solve.

  • Ensure the deployment captures a state file of the changes made.
  • Workflow around the previews and deployments.
  • Easily automated policy checks and tests.

This can be a really useful tool to bring infrastructure code maintainability directly into the the lifecycle of the application.

It's subjective to those in DevOps whether this would also apply for "Day 0-2" type operations, which are typically less frequently changed resources such as account settings, VPC, and other more static resources.

However, with a team experienced with Go or other tooling, I could see that this would provide a way to have much more programmatic control, loops, and other external libraries used, without resorting to the HCL DSL way of doing resource looping and inputs.

First Pass

First impression was very positive!

Basic steps:

  • brew install pulumi
  • pulumi new aws-go
  • Entered name of test stack such as aws-vpc.
  • Copied the VPC snippet from their docs and then plugged in my own tag for naming, which by default wasn't included.
  • Reproduced the example for pulumi.String().1
package main

import (
    "flag"

    petname "github.com/dustinkirkland/golang-petname"
    "github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ec2"
  "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)

var (
    words     = flag.Int("words", 2, "The number of words in the pet name")
    separator = flag.String("separator", "-", "The separator between words in the pet name"))

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
        conf := config.New(ctx, "")
        stage := conf.Require("stage")
        petname := petname.Generate(*words, *separator)
        _, err := ec2.NewVpc(ctx, stage, &ec2.VpcArgs{
            CidrBlock: pulumi.String("10.0.0.0/16"),
            Tags: pulumi.StringMap{
                "Name": pulumi.String(strings.Join([]string{stage, petname}, "-")),
            },
        })
        if err != nil {
            return err
        }

        return nil
    })
}

Positive Observations

  • Running pulumi destroy left the stack in the console for full plan history and auditing. To remove the stack from the web you'd run: pulumi stack rm dev. This is similar to how terraform cloud workspaces work and gives confidence of easier auditing by default.
  • The console experience and browser integration was beautifully done.
  • pulumi preview --emoji provided a very clean and succint summary of changes.
  • pulumi up also was very clean, and allowed a selection to expand the details as well.
  • Browser for stack provides full metadata detail, resource breakdown, audit history, and more.

Great Console Preview & Interaction Experience

  • The Pulumi docs for Azure DevOps were pretty solid! Full detail and walk through. As an experienced PowerShell developer, I was pleasantly suprised by quality PowerShell code that overall was structured well.2

  • Set some values via yaml easily by: 'pulumi config set --path 'stage' 'dev' which results in:

config:
  mystack:stage: dev
  aws:region: myregion

This is then read via:

conf := config.New(ctx, "")
stage := conf.Require("stage")

Things To Improve

  • Missing the benefit of Terraform module registry.
  • Pulumi Crosswalk sounds pretty awesome to help with this. However, I wasn't able to find the equivalent of a "crosswalk module library" to browse so that part might be a future improvement.

This document link: AWS Virtual Private Cloud (VPC) | Pulumi seemed great as a tutorial, but wasn't clear immediately on how I could use with Go.

I looked at the aws ยท pkg.go.dev but didn't see any equivalent to the documented awsx package shown from nodejs library.

Finally, found my answer.

Pulumi Crosswalk for AWS is currently supported only in Node.js (JavaScript or TypeScript) languages. Support for other languages, including Python, is on the future roadmap. Pulumi Crosswalk for AWS | Pulumi

I wish this was put as a big disclaimer right up at the top of the crosswalk section to ensure it was very clear.


  1. This feels very similar in style to the AWS SDK which doesn't allow just string values, but requires pointers to strings and thus wraps up the strings with expressions such as aws.String(

  2. Subjective, but I noticed boolean values instead of switches, which would slightly simplify the build scripts, but is more of a "nit" than a critical issue. Using if blocks instead of switch might also clean things up, but overall the script was pretty well written, which seems rare in vendor provided PowerShell examples. ๐Ÿ‘ 

Go R1 Day 64

progress

Wrote: First Pass with Pulumi

At $work, I'm working primarily with Go developers. This was an exploration of using Go for infrastructure.

Read a bit on CDK for Terraform as well, which seems interesting.

SweetOps Slack Archive

Just wanted to give props to the Cloudposse team lead by Erik Osterman @eosterman.

Slack provides a great community chat experience, but there are quite a few problems about using it for Q&A. 1 Since the free plans for communities hide content over 10,000 messages, a healthy community will go over this quickly.

With all the great conversations, I want to find prior discussions to benefit from topics already covered.

Cloudposse archives the community discussions so they can be searched.

Cloudposse archives the community discussions for future searches here: SweetOps Archive.

Pro-Tip Search Aliases

If you use Alfred you can setup an alias for this, or use a Chrome Search Engine Alias. To use a Chrome search engine alias, go to: Search Engines and add a new entry.

  • Search Engine: cloudposse
  • Keyword: cloudposse
  • URL with %s in place of query: https://archive.sweetops.com/search?query=%s

For any future search, just type in cloudposse in the searchbar and whatever you type after that will open up in the archive search.

Search Using Alfred

Search Using Chrome Search Engine Alias


  1. I don't think Cloudposse or many others deny that Slack is "inferior" for threaded conversation to a tool like Discourse. However, despite it being a "walled garden", it's a lot easier to get engagement there than a forum from what I understand. This solution provides a nice middle ground by giving the ease of Slack, while ensuring great conversation is still captured and able to be found. 

Go R1 Day 63

progress

  • Did some work on Go regex processing with a new linting tool concept I have for "semantic line breaks".
  • I've forced myself to apply TDD from the get go, so it's been slow going initially to abstract my functions to be testable without being run as a CLI tool.
  • Started on the run test for simulating the cli call as well.

Go R1 Day 62

progress

  • Worked with mocks.

Still using the is package to test.

if !reflect.DeepEqual(want, spySleepPrinter.Calls) {
  t.Errorf("wanted calls %v got %v", want, spySleepPrinter.Calls)
}

was replaced with:

is.Equal(spySleepPrinter.Calls, want) // spySleepPrinter shows correct order of calls

Go is messing with my head with how everything gets simplified to the lowest common interface when possible. Instead of buffer, I'd want to use io.Writer for example. This abstraction is where there is so much power, but it requires a deeper understanding of following the dependency tree to know what properly satisfies the interface. I'm finding that one layer isn't enough sometimes with lower level interfaces, and requires getting comfortable with more low level packages from the standard library. Pretty cool that I didn't need to do anything more complex to do a comparison.

When To Use Mocking

Without mocking important areas of your code will be untested. In our case we would not be able to test that our code paused between each print but there are countless other examples. Calling a service that can fail? Wanting to test your system in a particular state? It is very hard to test these scenarios without mocking. 1

Other Good Insights

"When to use iterative development? You should use iterative development only on projects that you want to succeed." - Martin Fowler

I really agree with this next quote. I've seen this happen so often with the pressures of a project, and feel it's the excuse that causes the escalation of technical debt that becomes difficult to untangle retroactively.

Try to get to a point where you have working software backed by tests as soon as you can, to avoid getting in rabbit holes and taking a "big bang" approach. 1