Skip to content

2020🔗

Compiling a Custom Provider and Including for Terraform Cloud

Assumptions

  • You are familiar with the basics of setting up Go and can run basic Go commands like go build and go install and don't need much guidance on that specific part.
  • You have a good familiarity with Terraform and the concept of providers.
  • You need to include a custom provider which isn't included in the current registry (or perhaps you've geeked out and modified one yourself 😁).
  • You want to run things in Terraform Enterprise ☁.

Terraform Cloud

For Terraform Cloud, bundling is not allowed.

Instead, the "legacy" way of running this is to include the plugin directly in the directory that Terraform will be invoked on with terraform.d/plugins/linux_amd64 as the path containing the provider. See discussion: Using Community Providers With Terraform Cloud.

Part of my current walk-through (primarily using terraform-bundle) is relevant only for Terraform Enterprise, not Terraform Cloud. I missed the ending documentation section on the custom bundle requiring installation and not being supported in Terraform Cloud.

For the directions below, disregard the bundle aspect for Terraform Cloud, and instead focus on building the custom provider and including in the project directory as shown.

If you are willing to explore Atlantis, I bet something can be done with custom providers in there.

After following the custom provider build steps below, create a .terraformignore file in your project directory and put in the config below.

.terraform
.git
.gtm
*.tfstate

With a backend like below, I was actually able to get terraform cloud to run the custom provider and return the plan.

terraform {
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "myorg"

    workspaces {
      name = "terraform-artifactory"
    }
  }
}

If you get an error the first time you run this, see the troubleshooting section at the end.

Custom Providers Bundling

As of the time of this post, to include a custom provider with Terraform Enterprise, you need to create a custom terraform bundle bundle to package up the terraform package and any desired custom plugins.

This terraform bundle includes the terraform program, as well as any range of other providers that you want to include for running in the remote terraform workspace.

Before you go down this route, you should make sure that the Terraform Registry doesn't already include the provider you need.

Source Of Truth

For the most up to date directions, you can go through these directions:

Compiling the custom provider

In this example, I'm working with a provider for Jfrog Artifactory, which IMO has an atrocious management experience on the web. By compiling this custom provider, my goal was to provide a clean user, repository, and group management experience.

You need to target the platform for Go in the build step, as the Terraform Enterprise environment expects Linux and amd64 as the target.

git clone https://github.com/atlassian/terraform-provider-artifactory.git
git install .

# I use pwsh even on macOS 😁
#$ENV:GOOS='linux'
#$ENV:GOARCH='amd64'
#go build

#See troubleshooting section below. More robust than simple go build. This simplifies things and will generate all binaries for you

goreleaser build --snapshot

Get Stuff Setup

git clone https://github.com/hashicorp/terraform.git

To checkout a specific tagged version (recommended):

git checkout tags/<tag_name>

Quick little #devhack... Use Git Graph in Visual Studio Code to make working with busy repositories much easier. Yes, I'm no Vim magician. Sometimes a little visual help is much better than trying to do it all in cli. #heresy

Use Git Graph to Visually Navigate A Busy Repo and Checkout a Tagged Commit

Next, you'll want to install and validate your install worked. go install ensures that dependencies are downloaded, so once again the magic of Go wins the day. If you flip to a new tagged version, make sure to rerun the install so you have the correct version of the tooling available.

go install .
go install ./tools/terraform-bundle

For some reason, I had issues with the path picking it up in my current session, so for expediency, I just ran the next steps with the fully qualified path: /Users/sheldonhull/go/bin/terraform-bundle instead of terraform-bundle directly.

Grab an example of the configuration hcl file for the terraform-bundler from the link mentioned above. Then you can create this in the project directory or qualify it to a subdirectory if you want to save various bundle configuration files.

mkdir mybundles
New-Item -Path ./mybundles/terraform-bundle.hcl -ItemType File

Here is a trimmed down example config with what worked for me. See the bottom troubleshooting section for more details on why I did not adhere to the documented config from the README.

terraform {
  version = "0.12.28"
}
providers {
 artifactory = ["0.0.0"]
}

We need to include this plugin in a specific location for the bundle tool to do it's magic.

Also ensure you follow the naming convention for a provider.

To be recognized as a valid plugin, the file must have a name of the form terraform-provider-<NAME>

This is where PowerShell shines, and it's easy to make this path without issue using Join-Path in a way that also is fully cross-platform with macOS, Linux, or Windows (pick your poison)

try
{
    $version = terraform-bundle --version *>&1
    if ($version -notmatch '\d{1}[.]\d{2}[.]\d{1,2}') { throw "failed to run terraform bundle: $($_.Exception.Message)" }
}
catch
{
    Write-Host "Adding go bin/path to path so terraform-bundle can be resolved"
    $ENV:PATH += "${ENV:HOME}/go/bin/:$PATH"
}


$SOURCEHOST     ='github.com'  # any arbitrary value allowed per docs
$SOURCENAMESPACE='atlassian'    # any arbitrary value allowed per docs
$NAME           ='artifactory'
$OS             ='linux'
$ARCH           ='amd64'
$VERSION        = '0.0.0'
$PluginPath     = Join-Path plugins $SOURCEHOST $SOURCENAMESPACE $NAME $VERSION "${OS}_${ARCH}"
$null           = New-Item -Path $PluginPath -ItemType Directory -Force
Remove-Item -LiteralPath ./plugins -Recurse -Confirm:$false
New-Item plugins -ItemType directory -Force -ErrorAction silentlycontinue
Copy-Item ${ENV:HOME}/git/github/terraform-provider-artifactory/dist/terraform-provider-artifactory_linux_amd64/terraform-provider-artifactory -Destination (Join-Path plugins "terraform-provider-artifactory") -Force
terraform-bundle package -os=linux -arch=amd64 --plugin-dir ./plugins ./jfrog-bundle.hcl

Now to bundle this up

terraform-bundle package -os=linux -arch=amd64 jfrog-bundle.hcl

Troubleshooting

Problems Parsing the bundle configuration file

I ran into some issues with it parsing the configuration file as soon as I added the custom plugin. It reported unknown type for string *ast.ObjectType.

Here's what I looked at:

In the project, there is a tools/terraform-bundle/config.go that is responsible for parsing the hcl file.

First, the configuration looks correct in taking a string slice for the versions, and the source is a normal string.

type TerraformConfig struct {
    Version discovery.VersionStr `hcl:"version"`
}

type ProviderConfig struct {
    Versions []string `hcl:"versions"`
    Source   string   `hcl:"source"`
}

This seems to mean the configuration syntax of meets with the schema required by the configuration code.

terraform {
    version = "0.12.28"
}
providers {
    artifactory = {
        versions = ["0.1"]
        source = "example.org/myorg/artifactory"
    }
}

It looks like the configuration syntax from the example is a bit different from what is being successfully parsed. Instead of using the fully designated schema, I adjusted it to artifactory = ["0.0.0"] and it succeeded in parsing the configuration.

The help terraform-bundle package --help also provides an example indicating to just use the simple syntax and let it look for the provider in the default directory of ./plugins.

Failed to resolve artifactory provider 0.1: no provider exists with the given name

This next piece was a bit trickier to figure out. Once I enabled $ENV:TF_LOG = 'TRACE' I found some output showing an issue with the version of the provider.

2020/07/14 16:12:51 [WARN] found legacy provider "terraform-provider-artifactory"
plugin: artifactory (0.0.0)
- Resolving "artifactory" provider (0.1)...
- Checking for provider plugin on https://releases.hashicorp.com...

I went back to the provider project and installed goreleaser using: brew install goreleaser/tap/goreleaser which provided me the same tool to build the various packages for this provider. Build the provider by running goreleaser build --snapshot.

After reviewing the help in more detail, the following CLI content conflicts with the main README.md, so I had to experiment with various output methods and finally... success! 🎉

The message did provide a warning: found legacy provider "terraform-provider-artifactory-v2.0.0".

I tested and found it matched the local provider with 0.0.0 by running terraform providers and seeing the output:

2020/07/14 16:49:52 [TRACE] Meta.Backend: backend *remote.Remote supports operations
.
└── provider.artifactory

However, what to bundle correctly required simplifying the output to no nested directories.

What Actually Worked In Plugin Directory Was a simple flat directory

The output of the bundle was successful with

Fetching Terraform 0.12.28 core package...
2020/07/14 16:54:34 [TRACE] HTTP client HEAD request to https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip
2020/07/14 16:54:35 [TRACE] HTTP client GET request to https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip
Fetching 3rd party plugins in directory: ./plugins
2020/07/14 16:54:37 [DEBUG] checking for provider in "./plugins"
2020/07/14 16:54:37 [WARN] found legacy provider "terraform-provider-artifactory"
plugin: artifactory (0.0.0)
- Resolving "artifactory" provider (0.0.0)...
Creating terraform_0.12.28-bundle2020071421_linux_amd64.zip ...
All done!

Terraform Cloud Fails with terraform.tfstate detected

Since the local plugins seem to generate some tfstate for mapping the local plugin directory, I ensure you have a .terraformignore file in the root of your directory per the notes I provided at the beginning.

Terraform Enterprise detected a terraform.tfstate file in your working
directory: <VCS-REPO>/terraform.tfstate

Once I added the .terraformignore the apparent conflict with uploading a local tfstate on the plugins was resolved and the plan succeeded.

2020-07-13T14:00:00-05:00

PowerShell has some functionality that can help in pre-processing data by grouping and aggregating. If you are using ImportExcel this might be useful to pre-aggregate the results prior to an Excel workbook. If you are working with PowerShell and needing to do some quick measurement of objects, maybe this will be useful.

{{< gist sheldonhull "af7e3355953a2f6533c813d9ca220a7d" >}}

2020-07-06T12:00:00-05:00

Windows users, nice little win for making the great git-town tool even more accessible. 🎉

  • install scoop: iwr -useb get.scoop.sh | iex
  • scoop install git-town

This is one of my favorite tools for git workflow. If you use GitHub flow to keep a simple workflow, it's a life saver.

For example, on a branch and need to start a new bit of work to keep your commits atomic? switch to master > stash pending work > pull latest with rebase > create new branch > push branch to remote OR git town hack feat/tacos. Need to squash commits and ship to master? git town ship What about prune all those remote branches that have been merged? git town prune-branches This is one of my favorite git productivity tools (and it's written in Go 👍 so cross platform and fast)

2020-06-29T18:29:03+00:00

Visual Studio Code has a pretty great way to browse through themes by just selecting installed themes and using arrow keys to preview without apply. However, browsing those themes isn't quite so good, as you need to install to see the changes.

Ran across Vscode Themes which provides a really nice experience if you feel like changing things up on your editor.

Azure Data Studio SQL Notebook for Diagnostic Queries

Diagnostic Queries

Glenn Berry has long been known for producing the definitive diagnostic query set for various SQL Server versions. Between his amazing work and my favorite Brent Ozar First Responder Kit, you are pretty much set.

One of the things that can be painful though about running diagnostic queries is that it's a lot of small individual queries that you want to run and ideally save the results for review.

You can do this with dbatools and running queries individually, which is actually what I did a while back for a special support tool that dynamically split those queries into files and exported to xml for later import and review.

Azure Data Studio

I'm a big fan of Azure Data Studio and as I'm not primarily focused right now on SQL Server administration, the feature-set perfectly fits my needs for running queries, doing some basic server administration, and overall just having a lighter weight solution to SSMS. Since I migrated to macOS, this provides me a nice cross-platform tool that I can use on Windows or macOS.

A great feature that has been continually improving is the Azure Data Studio notebooks. Not only can you run T-SQL notebooks now, but also PowerShell and python using whatever kernel you desire.

As part of this, you get the benefits of a nice intuitive structure to ad-hoc queries you might want to provide to someone with details on what it means and more. Additionally, the results are cached as part of the JSON so if you save the file and come back later you can review all the results that were pulled (and as a plus they render in GitHub viewer too).

Diagnostic Queries + Azure Data Studio + dbatools = 🎉

To merge the power of all 3 technologies, you can use dbatools to export the diagnostic queries for a targeted SQL server version as an Azure Data Studio Notebook. Pretty freaking cool.

To get started on this just make sure you have the latest dbatools: Install-Module dbatools -confirm:$false

Then generate a new Azure Data Studio Notebook like this:

# This will create the notebook in whatever location you currently are in
$Version = 2017
New-DbaDiagnosticAdsNotebook -Path "DiagnosticNotebook${Version}.ipynb" -TargetVersion $Version

Open up this new notebook and enjoy the result! To make reading easier, you can issue the command to "collapse all cells" and the queries will be minimized allowing you to read through all the query options.

Note that even the description of the queries is provided in the notebook, providing insight on the purpose of the query.

{{< admonition type="warning" title="Warning" >}}

As always, make sure you are careful before just running all queries by default against a production server. Some queries take heavy resources and might not be appropriate to run in the middle of a production workflow.

{{< /admonition >}}

Shows the diagnostic query view in Azure Data Studio

2020-06-24T16:03:49+00:00

After using Windows versions of launchers that tried to emulate Alfred, I finally took the plunge and bought Alfred's Powerpack. The buyer remorse of paying > $60 on a "shortcut" app hasn't really set in as I'm seeing such a wealth of great features that I think the gains as a developer and shortcut aficionado will be well worth the cost in my workflow. So far, highly recommend. The best part for me is the easy plugging in of bash/pwsh scripts to execute on demand for quick productivity tasks without having to navigate and open it up in the terminal.

Alfred Workflows

My Experience Switching To A Macbook Pro From Windows

{{< admonition type="Info" title="update 2021-05-19" open="true">}}

updated with fresh thoughts

Regarding stability issues, I'd say those initial stability issues haven't continued, so I'm very happy with the overall stability. The only thing that really gave me pain was DisplayLink drivers, which seem to always be a pain on Windows or MacOS.

{{< /admonition >}}

My background

My background has been very strongly focused on the .NET world, with a focused start on SQL Server, later branching into learning PowerShell. This world was heavily focused on Windows.

Why switch

Having focused on Windows for so long, using a Macbook for development always intrigued me, but was actually not the driving factor. Poor specs and performance issues with the currently issued laptop kept impacting my work, causing impact to my efforts. As my work right now is heavily focused in DevOps oriented areas, I found that the reliance on tooling that only worked in Windows over time has greatly reduced.

While I'm very excited for WSL2 with Windows, what I kept feeling like I was doing was battling my system instead of accomplishing the work I needed to get done. As someone who has been an obsessive tweaker of whatever OS I'm on, I've actually found my desire for full customization diminishing as the development work becomes more interesting. In this case, being able to work with more linux tooling without as much conflict was appealing.

Additionally, the windows specs for the workbooks I had available were definitely subpar to what a highend macbook could offer.

So I took the plunge, was approved thanks to a supportive manager, and now am working exclusively on a 16" Macbook Pro I9.

Setup

Learned some Ansible on the fly and configured everything pretty much from the start with Ansible. Overall, that made it a much better developer onboarding experience than trying to write my initial chocolatey package bootstrap script.

Painpoints

:(fas fa-external-link-alt): DisplayLink installation was painful for docking usage. Gotta be careful during updates.

:(fas fa-bullseye): No aeropeek

:(fas fa-window-restore): No automatic window snapping without third party apps

:(fas fa-keyboard): Text editing shortcuts

:(fas fa-home): Absolutely despise the changed home/end and selection behavior. Trying to remap this is painful. I've limited my remapping to try and adjust to the way it works natively, but it's difficult. VSCode remapping for certain actions is also tricky, but possible.

Wins

:(fas fa-search): So far I've found Finder to be really good and much more intuitive than Explorer in Windows.

:(fas fa-folder): The consistency of app placement makes finding things to run quick, whereas in Windows, finding the app can be a beast at times. Think about this from a new Windows developer perspective. Go to Program Files (x86), or Program Files, but some go into AppData, and then settings in ProgramData, but sometimes that program prefers to put some of the configuration data in the Program directory and sometimes in ProgramData.... unless they want to put it in AppData.... and then Local/Roaming etc. It gets really really confusing quick. That's why tools like Everything are so helpful!

:(fab fa-docker): Docker startup is 🚀 FAST. I'm talking about an update for Docker, and install restart and in a few seconds it's back up and running. I've been so used to it being minutes on Windows. Mounted drive performance isn't great, but neither is it on Windows.

:(fas fa-beer): I love Chocolatey & Scoop on Windows, but I'm seeing some advantages to a central repository containing all the packages, instead of each package being the responsibility of maintainers to keep up to date. This makes the contribution phase much more difficult in Chocolatey. I've written very complex Choco packages for my company, but haven't yet setup a community one, whereas Brew, Scoop, and others have a central repository that you can easily submit a push request to with improvements or new additions without having to manage the autorefresh of the packages. A minor, but important distinction to me, as the ease of contributing must be there for more people to engage in it. {{< typeit >}}Brew is stellar.{{< /typeit >}}

:(fab fa-windows): Not having Windows as my OS has helped me go cold turkey as much as possible on running more workloads in Docker. Visual Studio Docker workspaces are absolutely incredible, and paired with a good tool like InvokeBuild with PowerShell, you have a stellar setup than can easily bootstrap itself on a new machine with ease.

Quirks

:(fas fa-mouse): As of this time, mouse cursor not auto-hiding in full screen video without work arounds on certain sites.

:(fas fa-long-arrow-alt-right): Experimenting with some apps resulted in 2 full system crashes in first 3 weeks of using, so stability while good wasn't as stellar as I was hoping. Just like Windows, it all depends on what you are running.

:(fas fa-long-arrow-alt-right): Massive lag on bluetooth and even Logitech Unifying receiver based mouse and keyboard, enough to make them unusable. Others seem to have had similar issues when I searched.

:(fas fa-long-arrow-alt-right): Need to buy a powered hub to expand ports, as only USB-C resulting in all my peripherals not working.

:(fas fa-long-arrow-alt-right): Docks don't provide enough power for a macbook pro at times. Gone are the days of a slick dock that my laptop locks into. Get used to running cables.

:(fas fa-long-arrow-alt-right): Had to google "how to remove bonks" to get the annoying keyboard sound effect from driving my insane. This required editing: /DefaultKeyBinding.dict. Seriously, this was just silly.

:(fas fa-long-arrow-alt-right): I find the power button the mac annoying. Tends to have a mind of it's own when I'm thinking "just start darn you!"

Development Experience

I feel kinda strange as I've not needed to dive into system internals like I felt I needed with Windows.

At somepoint, I needed to touch the registry, work through service quirks, .NET library conflicts and more.

Overall, it feels like things are just easier and I fight less with development tools and libraries.

Perhaps the most hands on portion is just making sure binaries get to PATH as there is no real "global variable" like you have in Windows. Instead, this is normally managed in the user's .bashrc file, or in my case I use .profile and a powershell file to load my own overrides.

PowerShell & Shells

I put work into using zsh as my default, with a bunch of customizations. I still came back to PowerShell ❤️.

PowerShell is a fantastic default shell for macOS, and allowed me to immediately get value from the terminal without having to use a specific syntax to that shell (if it isn't POSIX compliant like Fish)

With PSReadline, the editor experience and autocomplete on my history is fantastic and I have no complaints.

I'm trying to keep an open mind and not "hate on bash/fish/zsh", as they have a long history. I can see if someone has a background in Linux only, that PowerShell is too much of a paradigm change to adopt without a desire to explore it.

For those with experience in more object oriented tooling, it will be more natural in my opinion than the quirks of bash scripts.1

With ConvertFrom-Json being such as magical tool, I've been able to mix and match native tooling with PowerShell automagic cmdlets and get some great productive results.

Shell Customization

With my dotfiles managed by chezmoi, my terminal in any environment looks great as I'm using starship

Apps

The ecosystem for nice dev tools feels better. I bought Alfred Microblog Post and Dash and find them really useful.

Verdict

So far, the experience has been one I'd repeat. For me, I've actually accomplished more, and gotten aware from my desk more with the improved quality of the mobile experience and touchpad.

Would I do it again? Currently yes.


  1. After digging through the mess of bash if comparison blocks, I found the PowerShell ternary operator very readable. (Test-Path $File -PathType Leaf) ? (Write-Host "😀 Do something with positive match and process") : (Write-Host "😢 Sad panda") Since all the bash tools are pretty much accessible from your pwsh prompt and scripts simply by being called, you gain all the perks of a clear readable scripting language, while still using native tooling. It's not better than bash, just different. For those experienced in bash and not really desiring to try something new, no problem! For those looking to try something new, I think it's a great way to write a cross platform script, and a good default shell. 

2020-06-17T22:32:32+00:00

Here's a cool way to visualize runspaces and how they work asynchronously.

$IndentLevel = 2
Get-Random; @(1..100) | ForEach-Object -Parallel {
    $i = $_
    Start-Sleep -Milliseconds (Get-Random -Minimum 100 -Maximum 1500)
    "{0,$($i * $using:IndentLevel)}" -f $i | Write-Host
}

The random delay can help show how some tasks finish out of order. For example, running that might result in this in the console:

       4
 1
     3
   2
             7
         5
                 9
           6
                  10
               8

2020-06-10T22:43:37+00:00

Set an environment variable export DOCKER_BUILDKIT=1 to enable a much cleaner Dockerfile output when you don't need verbose debug level information. Reference: Buildkit

2020-06-05T20:06:36+00:00

brew install micro resulted in my finding what I think will be my new go to cli editor. Very impressed so far. I've got too many things to learn to be fluent in vim, and this cross platform cli tool might be my new best friend for more command line-fu. Micro