Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-m-pix4d committed Jan 30, 2024
1 parent 3fe05f1 commit 395f4d3
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 51 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,12 @@ NON ignorable errors:

# Detach existing resources

summon aws-vault exec pix4d-users -- terraform -chdir=prod/cloud-products/crane-camera-a-c plan -no-color > detach.plan.txt


========FIXME


The reason of existence of this command is in the same spirit of `terravalet import`: it is true that `terraform state rm` allows to remove _individual_ resources, but when a real-world resource is composed of multiple terraform resources, using `terraform state rm` becomes tedious and error-prone.

Thus, `terravalet detach` creates all the `state rm` commands for you.
Expand Down
17 changes: 2 additions & 15 deletions cmddetach.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
)

func doDetach(statePath string, upPath string, resource string) error {
func doDetachOld(statePath string, upPath string, resource string) error {

Check failure on line 11 in cmddetach.go

View workflow job for this annotation

GitHub Actions / all (1.21.x, ubuntu-latest, v3.34.0)

func `doDetachOld` is unused (unused)
stateFile, err := os.Open(statePath)
if err != nil {
return fmt.Errorf("detach: opening the state file: %s", err)
Expand All @@ -27,7 +27,7 @@ func doDetach(statePath string, upPath string, resource string) error {
}

var bld strings.Builder
generateDetachScript(&bld, addresses)
generateRemoveScript(&bld, addresses)
_, err = upFile.WriteString(bld.String())
if err != nil {
return fmt.Errorf("detach: writing script file: %s", err)
Expand Down Expand Up @@ -84,16 +84,3 @@ func matchResource(index, resource string) bool {
}
return false
}

func generateDetachScript(wr io.Writer, addresses []string) {
fmt.Fprintf(wr, `#! /bin/sh
# DO NOT EDIT. Generated by https://github.com/pix4D/terravalet
#
# This script will detach %d items.
set -e
`, len(addresses))
for _, addr := range addresses {
fmt.Fprintf(wr, "\nterraform state rm '%s'\n", addr)
}
}
23 changes: 0 additions & 23 deletions cmddetach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"os"
"strings"
"testing"

"github.com/go-quicktest/qt"
Expand Down Expand Up @@ -104,25 +103,3 @@ func TestMatchResources(t *testing.T) {
qt.Assert(t, qt.IsNil(err))
qt.Assert(t, qt.DeepEquals(have, want))
}

func TestGenerateDetachScript(t *testing.T) {
addresses := []string{
`module.a.b.c["foo"]`,
`module.a.b.d["foo.AN-"]`,
}
var bld strings.Builder
want := `#! /bin/sh
# DO NOT EDIT. Generated by https://github.com/pix4D/terravalet
#
# This script will detach 2 items.
set -e
terraform state rm 'module.a.b.c["foo"]'
terraform state rm 'module.a.b.d["foo.AN-"]'
`

generateDetachScript(&bld, addresses)
qt.Assert(t, qt.Equals(bld.String(), want))
}
54 changes: 54 additions & 0 deletions cmdremove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"fmt"
"io"
"os"
"strings"
)

func doRemove(planPath string, upPath string) error {
planFile, err := os.Open(planPath)
if err != nil {
return fmt.Errorf("remove: opening the plan file: %s", err)
}
defer planFile.Close()

upFile, err := os.Create(upPath)
if err != nil {
return fmt.Errorf("remove: creating the up file: %s", err)
}
defer upFile.Close()

toCreate, toDestroy, err := parse(planFile)
if err != nil {
return fmt.Errorf("remove: parsing plan: %s", err)
}
if toCreate.Size() > 0 {
return fmt.Errorf("remove: plan contains resources to create: %v",
sorted(toCreate.List()))
}

var bld strings.Builder
generateRemoveScript(&bld, sorted(toDestroy.List()))
_, err = upFile.WriteString(bld.String())
if err != nil {
return fmt.Errorf("remove: writing script file: %s", err)
}

return nil
}

func generateRemoveScript(wr io.Writer, addresses []string) {
fmt.Fprintf(wr, `#! /bin/sh
# DO NOT EDIT. Generated by https://github.com/pix4D/terravalet
# This script will remove %d items.
set -e
`, len(addresses))
for _, addr := range addresses {
fmt.Fprintf(wr, "terraform state rm '%s'\n", addr)
}
fmt.Fprintln(wr)
}
30 changes: 30 additions & 0 deletions cmdremove_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"strings"
"testing"

"github.com/go-quicktest/qt"
)

func TestGenerateRemoveScript(t *testing.T) {
addresses := []string{
`module.a.b.c["foo"]`,
`module.a.b.d["foo.AN-"]`,
}
var bld strings.Builder
want := `#! /bin/sh
# DO NOT EDIT. Generated by https://github.com/pix4D/terravalet
#
# This script will remove 2 items.
set -e
terraform state rm 'module.a.b.c["foo"]'
terraform state rm 'module.a.b.d["foo.AN-"]'
`

generateRemoveScript(&bld, addresses)
qt.Assert(t, qt.Equals(bld.String(), want))
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ require (
github.com/dexyk/stringosim v0.0.0-20170922105913-9d0b3e91a842
github.com/go-quicktest/qt v1.101.0
github.com/google/go-cmp v0.6.0
github.com/rogpeppe/go-internal v1.11.0
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
)

require (
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/tools v0.1.12 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
28 changes: 16 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,27 @@ var (
)

func main() {
os.Exit(Main())
}

func Main() int {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
return 1
}
return 0
}

type args struct {
type Args struct {
Rename *RenameCmd `arg:"subcommand:rename" help:"rename resources in the same root environment"`
MoveAfter *MoveAfterCmd `arg:"subcommand:move-after" help:"move resources from one root environment to AFTER another"`
MoveBefore *MoveBeforeCmd `arg:"subcommand:move-before" help:"move resources from one root environment to BEFORE another"`
Import *ImportCmd `arg:"subcommand:import" help:"import resources generated out-of-band of Terraform"`
Detach *DetachCmd `arg:"subcommand:detach" help:"detach resources from Terraform state"`
Remove *RemoveCmd `arg:"subcommand:remove" help:"remove resources"`
Version *struct{} `arg:"subcommand:version" help:"show version"`
}

func (args) Description() string {
func (Args) Description() string {
return "terravalet - helps with advanced Terraform operations\n"
}

Expand Down Expand Up @@ -65,14 +70,13 @@ type ImportCmd struct {
SrcPlanPath string `arg:"--src-plan,required" help:"path to the SRC terraform plan in JSON format"`
}

type DetachCmd struct {
Up string `arg:"required" help:"path of the up script to generate (NNN_TITLE.up.sh)"`
State string `arg:"required" help:"path to to the output of 'terraform show -no-color -json'""`
Resource string `arg:"--resource,required" help:"name of the high-level resource to detach (will match multiple terraform resources)"`
type RemoveCmd struct {
Up string `arg:"required" help:"path of the up script to generate (NNN_TITLE.up.sh)"`
Plan string `arg:"required" help:"path to to the output of 'terraform plan -no-color'"`
}

func run() error {
var args args
var args Args

parser := arg.MustParse(&args)
if parser.Subcommand() == nil {
Expand All @@ -93,9 +97,9 @@ func run() error {
case args.Import != nil:
cmd := args.Import
return doImport(cmd.Up, cmd.Down, cmd.SrcPlanPath, cmd.ResourceDefs)
case args.Detach != nil:
cmd := args.Detach
return doDetach(cmd.State, cmd.Up, cmd.Resource)
case args.Remove != nil:
cmd := args.Remove
return doRemove(cmd.Plan, cmd.Up)
case args.Version != nil:
fmt.Println("terravalet", fullVersion)
return nil
Expand Down
29 changes: 29 additions & 0 deletions terravalet_script_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// This file runs tests using the 'testscript' package.
// To understand, see:
// - https://github.com/rogpeppe/go-internal
// - https://bitfieldconsulting.com/golang/test-scripts

package main

import (
"os"
"testing"

"github.com/rogpeppe/go-internal/testscript"
)

func TestMain(m *testing.M) {
// The commands map holds the set of command names, each with an associated
// run function which should return the code to pass to os.Exit.
// When [testscript.Run] is called, these commands are installed as regular
// commands in the shell path, so can be invoked with "exec".
os.Exit(testscript.RunMain(m, map[string]func() int{
"terravalet": Main,
}))
}

func TestScript(t *testing.T) {
testscript.Run(t, testscript.Params{
Dir: "testdata",
})
}
50 changes: 50 additions & 0 deletions testdata/cmd-remove.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
exec terravalet remove --up=foo_up.sh --plan=detach.plan.txt
! stderr .
cmp foo_up.sh foo_up.sh.want

-- foo_up.sh.want --
#! /bin/sh
# DO NOT EDIT. Generated by https://github.com/pix4D/terravalet
# This script will remove 6 items.

set -e

terraform state rm 'module.github.github_branch_default.default["foo-c"]'
terraform state rm 'module.github.github_repository.repos["foo-c"]'
terraform state rm 'module.github.github_repository_autolink_reference.repo_autolinks["foo-c.AN-"]'
terraform state rm 'module.github.github_repository_autolink_reference.repo_autolinks["foo-c.CV-"]'
terraform state rm 'module.github.github_repository_autolink_reference.repo_autolinks["foo-c.OPF-"]'
terraform state rm 'module.github.github_repository_collaborators.repo_collaborators["foo-c"]'

-- detach.plan.txt --
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
- destroy

Terraform will perform the following actions:

# module.github.github_branch_default.default["foo-c"] will be destroyed
# (because key ["foo-c"] is not in for_each map)
- resource "github_branch_default" "default" {

# module.github.github_repository.repos["foo-c"] will be destroyed
# (because key ["foo-c"] is not in for_each map)
- resource "github_repository" "repos" {

# module.github.github_repository_autolink_reference.repo_autolinks["foo-c.AN-"] will be destroyed
# (because key ["foo-c.AN-"] is not in for_each map)
- resource "github_repository_autolink_reference" "repo_autolinks" {

# module.github.github_repository_autolink_reference.repo_autolinks["foo-c.CV-"] will be destroyed
# (because key ["foo-c.CV-"] is not in for_each map)
- resource "github_repository_autolink_reference" "repo_autolinks" {

# module.github.github_repository_autolink_reference.repo_autolinks["foo-c.OPF-"] will be destroyed
# (because key ["foo-c.OPF-"] is not in for_each map)
- resource "github_repository_autolink_reference" "repo_autolinks" {

# module.github.github_repository_collaborators.repo_collaborators["foo-c"] will be destroyed
# (because key ["foo-c"] is not in for_each map)
- resource "github_repository_collaborators" "repo_collaborators" {

Plan: 0 to add, 0 to change, 6 to destroy.
31 changes: 31 additions & 0 deletions testdata/remove/01_plan.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
- destroy

Terraform will perform the following actions:

# module.github.github_branch_default.default["foo-c"] will be destroyed
# (because key ["foo-c"] is not in for_each map)
- resource "github_branch_default" "default" {

# module.github.github_repository.repos["foo-c"] will be destroyed
# (because key ["foo-c"] is not in for_each map)
- resource "github_repository" "repos" {

# module.github.github_repository_autolink_reference.repo_autolinks["foo-c.AN-"] will be destroyed
# (because key ["foo-c.AN-"] is not in for_each map)
- resource "github_repository_autolink_reference" "repo_autolinks" {

# module.github.github_repository_autolink_reference.repo_autolinks["foo-c.CV-"] will be destroyed
# (because key ["foo-c.CV-"] is not in for_each map)
- resource "github_repository_autolink_reference" "repo_autolinks" {

# module.github.github_repository_autolink_reference.repo_autolinks["foo-c.OPF-"] will be destroyed
# (because key ["foo-c.OPF-"] is not in for_each map)
- resource "github_repository_autolink_reference" "repo_autolinks" {

# module.github.github_repository_collaborators.repo_collaborators["foo-c"] will be destroyed
# (because key ["foo-c"] is not in for_each map)
- resource "github_repository_collaborators" "repo_collaborators" {

Plan: 0 to add, 0 to change, 6 to destroy.
18 changes: 18 additions & 0 deletions testdata/remove/01_up.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#! /bin/sh
# DO NOT EDIT. Generated by https://github.com/pix4D/terravalet
#
# This script will detach 6 items.

set -e

terraform state rm 'module.github.github_branch_default.default["crane-camera-ccqc"]'

terraform state rm 'module.github.github_repository.repos["crane-camera-ccqc"]'

terraform state rm 'module.github.github_repository_autolink_reference.repo_autolinks["crane-camera-ccqc.AN-"]'

terraform state rm 'module.github.github_repository_autolink_reference.repo_autolinks["crane-camera-ccqc.CV-"]'

terraform state rm 'module.github.github_repository_autolink_reference.repo_autolinks["crane-camera-ccqc.OPF-"]'

terraform state rm 'module.github.github_repository_collaborators.repo_collaborators["crane-camera-ccqc"]'

0 comments on commit 395f4d3

Please sign in to comment.