Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support --replace #9

Merged
merged 5 commits into from
Dec 21, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/wc-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- run: tfmv --version
- run: tfmv --help

- run: tfmv -j tfmv.jsonnet
- run: tfmv --replace "-/_"
working-directory: example
- run: diff main.tf expected_main.tf
working-directory: example
Expand Down
54 changes: 28 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
# tfmv

CLI to rename Terraform resources and modules and generate moved blocks.
You can rename blocks flexibly using [Jsonnet](https://jsonnet.org).

## Install
[MIT LICENSE](LICENSE) | [Install](docs/install.md)

```
go install github.com/suzuki-shunsuke/tfmv/cmd/tfmv@latest
```
CLI to rename Terraform resources and modules and generate moved blocks.

## Getting Started

1. Install tfmv
1. Checkout a repository
1. [Install tfmv](#install)
1. Checkout this repository

```sh
git clone https://github.com/suzuki-shunsuke/tfmv
Expand All @@ -26,21 +21,15 @@ resource "null_resource" "foo-prod" {}
```

Let's replace `-` with `_`.
You need to specify either `--replace` or `--jsonnet (-j)`.
In this case, let's use `--replace`.
[If you need more flexible renaming, you can use Jsonnet. For details, please see here](#jsonnet).

tfmv uses Jsonnet to rename resources flexibly.
[For details of Jsonnet, please see here](#jsonnet).

tfmv.jsonnet:

```jsonnet
std.native("strings.Replace")(std.extVar('input').name, "-", "_", -1)[0]
```

Run `tfmv -j tfmv.jsonnet`.
Run `tfmv --replace "-/_"`.
You don't need to run `terraform init`.

```sh
tfmv -j tfmv.jsonnet
tfmv --replace "-/_"
```

Then a resource name is changed and `moved.tf` is created.
Expand All @@ -66,15 +55,15 @@ moved {
You can also pass *.tf via arguments:

```sh
tfmv -j tfmv.jsonnet foo/aws_s3_bucket.tf foo/aws_instance.tf
tfmv --replace "-/_" foo/aws_s3_bucket.tf foo/aws_instance.tf
```

### Dry Run: --dry-run

With `--dry-run`, tfmv outputs logs but doesn't rename blocks.

```sh
tfmv -j tfmv.jsonnet --dry-run bar/main.tf
tfmv --replace "-/_" --dry-run bar/main.tf
```

### Change the filename for moved blocks
Expand All @@ -83,13 +72,13 @@ By default tfmv writes moved blocks to `moved.tf`.
You can change the file name via `-m` option.

```sh
tfmv -j tfmv.jsonnet -m moved_blocks.tf bar/main.tf
tfmv --replace "-/_" -m moved_blocks.tf bar/main.tf
```

You can also write moved blocks to the same file with renamed resources and modules.

```sh
tfmv -j tfmv.jsonnet -m same bar/foo.tf
tfmv --replace "-/_" -m same bar/foo.tf
```

### `-r` Recursive option
Expand All @@ -98,7 +87,7 @@ By default, tfmv finds *.tf on the current directory.
You can find files recursively using `-r` option.

```sh
tfmv -r -j tfmv.jsonnet
tfmv -r --replace "-/_"
```

The following directories are ignored:
Expand All @@ -109,7 +98,20 @@ The following directories are ignored:

## Jsonnet

tfmv uses [Jsonnet](https://jsonnet.org) to enable you to define a custom rename logic.
`--replace` is simple and useful, but sometimes you need more flexible renaming.
In that case, you can use `--jsonnet (-j)`.
[Jsonnet](https://jsonnet.org) is a powerful data configuration language.

tfmv.jsonnet (You can change the filename freely):

```jsonnet
std.native("strings.Replace")(std.extVar('input').name, "-", "_", -1)[0]
```

```sh
tfmv -j tfmv.jsonnet
```

You need to define Jsonnet whose input is each resource and output is a new resource name.
tfmv passes an input via External Variables.
You can access an input by `std.extVar('input')`.
Expand Down
8 changes: 8 additions & 0 deletions cmdx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,11 @@ tasks:
description: Format GO codes
usage: Format GO codes
script: bash scripts/fmt.sh
- name: rm-moved
description: Remove moved.tf
usage: Remove moved.tf
short: rmm
script: |
set -euo pipefail
find example -name moved.tf | xargs rm
find example -name moved_blocks.tf | xargs rm
122 changes: 122 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Install

tfmv is written in Go. So you only have to install a binary in your `PATH`.

There are some ways to install tfmv.

1. [Homebrew](#homebrew)
1. [aqua](#aqua)
1. [GitHub Releases](#github-releases)
1. [Build an executable binary from source code yourself using Go](#build-an-executable-binary-from-source-code-yourself-using-go)

## Homebrew

You can install tfmv using [Homebrew](https://brew.sh/).

```sh
brew install suzuki-shunsuke/tfmv/tfmv
```

## aqua

[aqua-registry >= v4.282.0](https://github.com/aquaproj/aqua-registry/releases/tag/v4.282.0)

You can install tfmv using [aqua](https://aquaproj.github.io/).

```sh
aqua g -i suzuki-shunsuke/tfmv
```

## Build an executable binary from source code yourself using Go

```sh
git clone https://github.com/suzuki-shunsuke/tfmv
cd tfmv
go install ./cmd/tfmv
```

> [!WARNING]
> Unfortunately, `go install github.com/suzuki-shunsuke/tfmv/cmd/tfmv@latest` doesn't work because tfmv uses a replace directive in go.mod.
>
> ```console
> $ go install github.com/suzuki-shunsuke/tfmv/cmd/tfmv@latest
> go: github.com/suzuki-shunsuke/tfmv/cmd/tfmv@latest (in github.com/suzuki-shunsuke/[email protected]):
> The go.mod file for the module providing named packages contains one or
> more replace directives. It must not contain directives that would cause
> it to be interpreted differently than if it were the main module.
> ```

## GitHub Releases

You can download an asset from [GitHub Releases](https://github.com/suzuki-shunsuke/tfmv/releases).
Please unarchive it and install a pre built binary into `$PATH`.

### Verify downloaded assets from GitHub Releases

You can verify downloaded assets using some tools.

1. [GitHub CLI](https://cli.github.com/)
1. [slsa-verifier](https://github.com/slsa-framework/slsa-verifier)
1. [Cosign](https://github.com/sigstore/cosign)

### 1. GitHub CLI

You can install GitHub CLI by aqua.

```sh
aqua g -i cli/cli
```

```sh
version=v0.1.1
asset=tfmv_darwin_arm64.tar.gz
gh release download -R suzuki-shunsuke/tfmv "$version" -p "$asset"
gh attestation verify "$asset" \
-R suzuki-shunsuke/tfmv \
--signer-workflow suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml
```

### 2. slsa-verifier

You can install slsa-verifier by aqua.

```sh
aqua g -i slsa-framework/slsa-verifier
```

```sh
version=v0.1.1
asset=tfmv_darwin_arm64.tar.gz
gh release download -R suzuki-shunsuke/tfmv "$version" -p "$asset" -p multiple.intoto.jsonl
slsa-verifier verify-artifact "$asset" \
--provenance-path multiple.intoto.jsonl \
--source-uri github.com/suzuki-shunsuke/tfmv \
--source-tag "$version"
```

### 3. Cosign

You can install Cosign by aqua.

```sh
aqua g -i sigstore/cosign
```

```sh
version=v0.1.1
checksum_file="tfmv_${version#v}_checksums.txt"
asset=tfmv_darwin_arm64.tar.gz
gh release download "$version" \
-R suzuki-shunsuke/tfmv \
-p "$asset" \
-p "$checksum_file" \
-p "${checksum_file}.pem" \
-p "${checksum_file}.sig"
cosign verify-blob \
--signature "${checksum_file}.sig" \
--certificate "${checksum_file}.pem" \
--certificate-identity-regexp 'https://github\.com/suzuki-shunsuke/go-release-workflow/\.github/workflows/release\.yaml@.*' \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
"$checksum_file"
cat "$checksum_file" | sha256sum -c --ignore-missing
```
4 changes: 2 additions & 2 deletions example/foo/aws_s3_bucket.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
resource "aws_s3_bucket" "example-1" {
resource "aws_s3_bucket" "example_1" {
bucket = "test-1"
}

resource "aws_s3_bucket" "example-2" {
resource "aws_s3_bucket" "example_2" {
bucket = "test-2"
}
2 changes: 1 addition & 1 deletion example/main.tf
Original file line number Diff line number Diff line change
@@ -1 +1 @@
resource "null_resource" "foo-prod" {}
resource "null_resource" "foo_prod" {}
4 changes: 4 additions & 0 deletions pkg/cli/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (r *Runner) Run(ctx context.Context) error {
Recursive: flg.Recursive,
DryRun: flg.DryRun,
Args: flg.Args,
Replace: flg.Replace,
})
}

Expand All @@ -66,6 +67,7 @@ type Flag struct {
Moved string
LogLevel string
LogColor string
Replace string
Args []string
Help bool
Version bool
Expand All @@ -76,6 +78,7 @@ type Flag struct {
func parseFlags(f *Flag) {
flag.StringVarP(&f.Jsonnet, "jsonnet", "j", "", "Jsonnet file path")
flag.StringVarP(&f.Moved, "moved", "m", "moved.tf", "The destination file name")
flag.StringVar(&f.Replace, "replace", "", "Replace strings in block names. The format is <new>/<old>. e.g. -/_")
flag.StringVar(&f.LogLevel, "log-level", "info", "The log level")
flag.StringVar(&f.LogColor, "log-color", "auto", "The log color")
flag.BoolVarP(&f.Help, "help", "h", false, "Show help")
Expand All @@ -97,6 +100,7 @@ Options:
--version, -v Show sort-issue-template version
--jsonnet, -j Jsonnet file path
--recursive, -r If this is set, tfmv finds files recursively
--replace Replace strings in block names. The format is <new>/<old>. e.g. -/_
--dry-run Dry Run
--log-level Log level
--log-color Log color. "auto", "always", "never" are available
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func (c *Controller) Init(fs afero.Fs, stdout, stderr io.Writer) {
type Input struct {
File string
Dest string
Replace string
Args []string
Recursive bool
DryRun bool
Expand Down
26 changes: 24 additions & 2 deletions pkg/controller/jsonnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,37 @@ import (
"github.com/lintnet/go-jsonnet-native-functions/pkg/path/filepath"
"github.com/lintnet/go-jsonnet-native-functions/pkg/regexp"
"github.com/lintnet/go-jsonnet-native-functions/pkg/strings"
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
)

func (c *Controller) evaluate(block *Block, ja ast.Node) (string, error) {
type JsonnetRenamer struct {
node ast.Node
}

func NewJsonnetRenamer(logE *logrus.Entry, fs afero.Fs, file string) (*JsonnetRenamer, error) {
// read Jsonnet
logE.Debug("reading a jsonnet file")
b, err := afero.ReadFile(fs, file)
if err != nil {
return nil, fmt.Errorf("read a jsonnet file: %w", err)
}
// parse Jsonnet
logE.Debug("parsing a jsonnet file")
node, err := jsonnet.SnippetToAST(file, string(b))
if err != nil {
return nil, fmt.Errorf("parse a jsonnet file: %w", err)
}
return &JsonnetRenamer{node: node}, nil
}

func (j *JsonnetRenamer) Rename(block *Block) (string, error) {
b, err := json.Marshal(block)
if err != nil {
return "", fmt.Errorf("marshal a block: %w", err)
}
vm := NewVM(string(b))
result, err := vm.Evaluate(ja)
result, err := vm.Evaluate(j.node)
if err != nil {
return "", fmt.Errorf("evaluate Jsonnet: %w", err)
}
Expand Down
Loading
Loading