Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: release
on:
push:
tags: ['v*']

permissions:
contents: write

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # goreleaser derives the changelog and version from tag history
- uses: actions/setup-go@v7
with:
go-version-file: ./.go-version
- uses: goreleaser/goreleaser-action@v6

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow or composite action Medium

Unpinned 3rd party Action 'release' step
Uses Step
uses 'goreleaser/goreleaser-action' with ref 'v6', not a pinned commit hash
with:
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

homebrew:
needs: goreleaser
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: update tap formula (source build)
env:
TAG: ${{ github.ref_name }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
run: ./scripts/update-homebrew-formula.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/terrafmt
*.test
*.out
/dist/
74 changes: 74 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
version: 2

project_name: terrafmt

builds:
- env:
- CGO_ENABLED=0
flags:
- -trimpath
- -mod=vendor
mod_timestamp: '{{ .CommitTimestamp }}'
goos:
- linux
- darwin
- windows
- freebsd
- openbsd
- solaris
goarch:
- '386'
- amd64
- arm
- arm64
- riscv64
goarm:
- '6'
- '7'
ignore:
- goos: darwin
goarch: '386'
- goos: darwin
goarch: arm
- goos: darwin
goarch: riscv64
- goos: windows
goarch: arm
- goos: windows
goarch: riscv64
- goos: solaris
goarch: '386'
- goos: solaris
goarch: arm
- goos: solaris
goarch: arm64
- goos: solaris
goarch: riscv64
- goos: openbsd
goarch: arm
- goos: freebsd
goarch: riscv64
- goos: openbsd
goarch: riscv64
ldflags:
- -s -w
- -X github.com/katbyte/terrafmt/lib/version.Version={{ .Tag }}
- -X github.com/katbyte/terrafmt/lib/version.GitCommit={{ .ShortCommit }}

universal_binaries:
- replace: false # also ship a single fat darwin binary alongside amd64/arm64

archives:
- formats: [tar.gz]
format_overrides:
- goos: windows
formats: [zip]

checksum:
name_template: 'checksums.txt'

changelog:
filters:
exclude:
- '^Bump '
- 'merge main'
116 changes: 49 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,24 @@
[![Go Version](https://img.shields.io/github/go-mod/go-version/katbyte/terrafmt?color=00ADD8)](https://github.com/katbyte/terrafmt/blob/main/go.mod)
[![License](https://img.shields.io/github/license/katbyte/terrafmt?color=blue)](https://github.com/katbyte/terrafmt/blob/main/LICENSE)

A tool for extracting or formatting [Terraform](https://www.terraform.io/docs/) configuration embedded in [provider](https://www.terraform.io/docs/providers/index.html) code
A tool for extracting and formatting [Terraform](https://www.terraform.io/docs/) configuration embedded in other files, primarily intended to help with [provider](https://www.terraform.io/docs/providers/index.html) development.

## Install

### Local Install
### Homebrew

Use Go to install directly into your `$GOBIN` directory (e.g. `$GOPATH/bin`):
```console
brew install katbyte/tap/terrafmt
```

### Pre-built binaries

Binaries for linux, macOS, windows, freebsd, openbsd, and solaris are attached to each [release](https://github.com/katbyte/terrafmt/releases/latest).

### Go

```console
go get github.com/katbyte/terrafmt
go install github.com/katbyte/terrafmt@latest
```

## Usage
Expand All @@ -29,28 +37,21 @@ Information about usage and options can be found by using the `help` command:
terrafmt help
```

This tool can extract terraform blocks, run `terraform fmt` on the blocks and display the difference or update them in place.

The tool currently supports blocks with the following start and end lines:
terrafmt finds terraform blocks embedded in files, runs the equivalent of `terraform fmt` on them, and can display the difference or update them in place. It understands:

|start |end |
|--------------------|----|
|```hcl |``` |
|```tf |`, |
|```terraform |`, |
|return fmt.Sprintf(`|`, |
|return fmt.Sprintf(`|`) |
|return ` |` |
- **Markdown** (`.md`, `.markdown`, and other non-go files): fenced code blocks opened with ` ```hcl `, ` ```tf `, or ` ```terraform `
- **reStructuredText** (`.rst`): `.. code:: terraform` directives (block indentation is preserved)
- **Go** (`.go`): multiline string literals that look like terraform configuration, e.g. acceptance test configs returned by `fmt.Sprintf`

### Extract Terraform Blocks

Use the `blocks` command to extract blocks from a file:

![blocks](.github/images/blocks.png)

To output only the block content, separated by the null character, use the flags `--zero-terminated` or `z`.
To output only the block content, separated by the null character, use `--zero-terminated`/`-z`.

To output the blocks using a JSON structure, use the flags `--json` or `-j`. The format is
To output the blocks as JSON, use `--json`/`-j`:

```json
{
Expand All @@ -66,85 +67,66 @@ To output the blocks using a JSON structure, use the flags `--json` or `-j`. The
}
```

Go [format verbs](https://golang.org/pkg/fmt/) can be escaped in the output blocks by using the flags `--fmtcompat` or `-f`.

To output the blocks using a JSON structure, use the flags `--json` or `-j`. The format is

```json
{
"block_count": 1,
"blocks": [
{
"block_number": 1,
"start_line": 4,
"end_line": 9,
"text": "..."
}
]
}
```
Go [format verbs](https://golang.org/pkg/fmt/) (`%s`, `%d`, `%[1]q`, ...) can be escaped in the output blocks with `--fmtcompat`/`-f`.

### Show What Format Would Do

Use the `diff` command to see what would be formatted (files can also be piped in on stdin) :
Use the `diff` command to see what would be formatted (files can also be piped in on stdin):

![diff](.github/images/diff.png)
![diff](.github/images/diff.png)

For code files with printf verb formatting use the `-f` switch :
For go files containing format verbs use the `-f` switch:

![diff -f](.github/images/diff-f.png)

### Format File
### Format Files

Use the `fmt` command to format the blocks:
Use the `fmt` command to format blocks in place. It accepts a single file, stdin, or a directory to walk — combine with `--pattern`/`-p` to filter by file name:

![fmt](.github/images/fmt.png)

### Format Multiple

One can use find and egrep to format multiple files:
```shell
find . | egrep "html.markdown" | sort | while read f; do terrafmt fmt -f $f; done
./website/docs/d/api_management.html.markdown: 136 lines & formatted 0/1 blocks!
./website/docs/d/api_management_api.html.markdown: 79 lines & formatted 0/1 blocks!
./website/docs/d/api_management_group.html.markdown: 46 lines & formatted 0/1 blocks!
./website/docs/d/api_management_product.html.markdown: 52 lines & formatted 0/1 blocks!
./website/docs/d/api_management_user.html.markdown: 48 lines & formatted 0/1 blocks!
./website/docs/d/app_service.html.markdown: 139 lines & formatted 0/1 blocks!
./website/docs/d/app_service_certificate.html.markdown: 54 lines & formatted 0/1 blocks!
./website/docs/d/app_service_certificate_order.html.markdown: 79 lines & formatted 0/1 blocks!
```console
terrafmt fmt ./website --pattern '*.markdown'
terrafmt fmt ./internal --pattern '*_test.go' -f
```

### Exit codes

To help usage of `terrafmt` in workflows, some commands will return actionable exit codes.

If a Terraform parsing error is encountered in a block, the exit code is `2`.
To help usage of `terrafmt` in workflows, some commands return actionable exit codes.

If the command `diff` with the `--check` flag enabled encounters a formatting difference, it will return `4`. If a file contains both blocks with parsing errors and a formatting difference, it will combine the exit codes to return `6`. These codes can be tested using bitwise checks.
If a terraform parsing error is encountered in a block, the exit code is `2`.

Otherwise, `terrafmt` will return `1` on an error.
If the `diff` command with the `--check` flag enabled encounters a formatting difference, it will return `4`. If a file contains both blocks with parsing errors and a formatting difference, the codes combine to `6`. These can be tested using bitwise checks.

## Development and Testing
Otherwise, `terrafmt` returns `1` on an error.

This project uses [Go Modules](https://github.com/golang/go/wiki/Modules) for dependency management.
## Development

### Updating Dependencies
This project uses [Go Modules](https://github.com/golang/go/wiki/Modules) with a vendored `vendor/` directory.

```console
$ go get URL
$ go mod tidy
$ go mod vendor
make build # build the binary
make test # run the tests (with -race)
make lint # run golangci-lint
make lint-fix # run golangci-lint and apply autofixes
make fmt # gofmt/gofumpt/goimports the source
make depscheck # verify go.mod/go.sum/vendor are consistent
make check-all # build + test + lint + depscheck
```

### Unit Testing
When updating dependencies, re-vendor:

```console
$ go test ./...
go get <module>
go mod tidy
go mod vendor
```

### Local Install
## Releasing

Releases are cut by pushing a semver tag; CI ([goreleaser](https://goreleaser.com)) builds the binaries, publishes the GitHub release, and updates the [homebrew tap](https://github.com/katbyte/homebrew-tap) formula:

```console
$ go install .
git tag v0.6.0
git push origin v0.6.0
```
60 changes: 60 additions & 0 deletions scripts/update-homebrew-formula.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
#
# Regenerates the source-build formula in katbyte/homebrew-tap for a released tag.
# Usage: TAG=v0.6.0 [TAP_DRY_RUN=1] HOMEBREW_TAP_GITHUB_TOKEN=... ./scripts/update-homebrew-formula.sh
set -euo pipefail

TAG="${TAG:?set TAG to the release tag, e.g. v0.6.0}"
VERSION="${TAG#v}"

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

echo "==> fetching source tarball for ${TAG}..."
curl -sSfL "https://github.com/katbyte/terrafmt/archive/refs/tags/${TAG}.tar.gz" -o "$tmp/src.tar.gz"
SHA256="$(sha256sum "$tmp/src.tar.gz" | cut -d' ' -f1)"

cat > "$tmp/terrafmt.rb" <<FORMULA
# typed: false
# frozen_string_literal: true

# This file is generated by the terrafmt release workflow - do not edit by hand.
class Terrafmt < Formula
desc "Format terraform blocks embedded in files"
homepage "https://github.com/katbyte/terrafmt"
url "https://github.com/katbyte/terrafmt/archive/refs/tags/${TAG}.tar.gz"
sha256 "${SHA256}"
license "GPL-3.0-only"
head "https://github.com/katbyte/terrafmt.git", branch: "main"

depends_on "go" => :build

def install
system "go", "build", *std_go_args(ldflags: "-s -w -X github.com/katbyte/terrafmt/lib/version.Version=${TAG} -X github.com/katbyte/terrafmt/lib/version.GitCommit=homebrew")
end

test do
assert_match "${TAG}", shell_output("#{bin}/terrafmt version")
end
end
FORMULA

if [ "${TAP_DRY_RUN:-0}" = "1" ]; then
echo "==> dry run; generated formula:"
cat "$tmp/terrafmt.rb"
exit 0
fi

: "${HOMEBREW_TAP_GITHUB_TOKEN:?set HOMEBREW_TAP_GITHUB_TOKEN to a token with push access to katbyte/homebrew-tap}"
echo "==> pushing formula to katbyte/homebrew-tap..."
git clone --depth 1 "https://x-access-token:${HOMEBREW_TAP_GITHUB_TOKEN}@github.com/katbyte/homebrew-tap.git" "$tmp/tap"
mkdir -p "$tmp/tap/Formula"
cp "$tmp/terrafmt.rb" "$tmp/tap/Formula/terrafmt.rb"
cd "$tmp/tap"
git config user.name "terrafmt-release"
git config user.email "terrafmt-release@katbyte.me"
git add Formula/terrafmt.rb
git diff --cached --quiet && { echo "==> formula unchanged"; exit 0; }
git commit -m "terrafmt ${TAG}"
git push
echo "==> done"