Skip to content

Commit c8e1449

Browse files
Merge pull request #44 from Pix4D/move-before
add new command move-before
2 parents 4a860a9 + c288d04 commit c8e1449

File tree

8 files changed

+209
-14
lines changed

8 files changed

+209
-14
lines changed

CHANGELOG.md

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,34 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## Unreleased
9-
### Fixes
10-
### Breaking changes
11-
### Changes
8+
## [v0.7.0] - (Unreleased)
9+
1210
### New
1311

14-
## [v0.6.2] - (Unreleased)
12+
- New command `move-before`, to move resources to a root environment upstream in the dependency chain (see README for details):
13+
```
14+
$ terravalet move-before -h
15+
Usage: terravalet move-before --script SCRIPT --before BEFORE --after AFTER
16+
Options:
17+
--script SCRIPT the migration scripts; will generate SCRIPT_up.sh and SCRIPT_down.sh
18+
--before BEFORE the before root directory; will look for BEFORE.tfplan and BEFORE.tfstate
19+
--after AFTER the after root directory; will look for AFTER.tfstate
20+
```
21+
22+
- Simplify workflow for state move (see README for details).
23+
24+
### Breaking changes
25+
26+
- Rename command `move` to `move-after` (to be uniform with the new command `move-before`).
27+
- Command `move-after` now takes 3 (different) CLI options instead of the previous 6:
28+
```
29+
$ terravalet move-after -h
30+
Usage: terravalet move-after --script SCRIPT --before BEFORE --after AFTER
31+
Options:
32+
--script SCRIPT the migration scripts; will generate SCRIPT_up.sh and SCRIPT_down.sh
33+
--before BEFORE the before root directory; will look for BEFORE.tfplan and BEFORE.tfstate
34+
--after AFTER the after root directory; will look for BEFORE.tfstate and AFTER.tfstate
35+
```
1536

1637
### Changes
1738

@@ -54,8 +75,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5475

5576
## [v0.4.0] - (2021-01-25)
5677

57-
### Fixes
58-
5978
### Breaking changes
6079

6180
- Due to the introduction of subcommands, the CLI API has changed; now it must be invoked by specifying a subcommand. See section New for details.
@@ -133,3 +152,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
133152
[v0.4.0]: https://github.com/Pix4D/terravalet/releases/tag/v0.4.0
134153
[v0.5.0]: https://github.com/Pix4D/terravalet/releases/tag/v0.5.0
135154
[v0.6.0]: https://github.com/Pix4D/terravalet/releases/tag/v0.6.0
155+
[v0.6.1]: https://github.com/Pix4D/terravalet/releases/tag/v0.6.1
156+
[v0.7.0]: https://github.com/Pix4D/terravalet/releases/tag/v0.7.0
157+

cmdmoverename.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,58 @@ func doMoveAfter(script, before, after string) error {
141141
return nil
142142
}
143143

144+
func doMoveBefore(script, before, after string) error {
145+
beforePlanPath := before + ".tfplan"
146+
beforePlanFile, err := os.Open(beforePlanPath)
147+
if err != nil {
148+
return fmt.Errorf("opening the terraform BEFORE plan file: %v", err)
149+
}
150+
defer beforePlanFile.Close()
151+
152+
upPath := script + "_up.sh"
153+
upFile, err := os.Create(upPath)
154+
if err != nil {
155+
return fmt.Errorf("creating the up file: %v", err)
156+
}
157+
defer upFile.Close()
158+
159+
downPath := script + "_down.sh"
160+
downFile, err := os.Create(downPath)
161+
if err != nil {
162+
return fmt.Errorf("creating the down file: %v", err)
163+
}
164+
defer downFile.Close()
165+
166+
beforeCreate, beforeDestroy, err := parse(beforePlanFile)
167+
if err != nil {
168+
return fmt.Errorf("parse BEFORE plan: %v", err)
169+
}
170+
if beforeCreate.Size() == 0 {
171+
return fmt.Errorf("BEFORE plan does not contain resources to create")
172+
}
173+
if beforeDestroy.Size() > 0 {
174+
return fmt.Errorf("BEFORE plan contains resources to destroy: %s",
175+
sorted(beforeDestroy.List()))
176+
}
177+
178+
upMatches, downMatches := matchExact(beforeCreate, beforeCreate)
179+
180+
beforeStatePath := before + ".tfstate"
181+
afterStatePath := after + ".tfstate"
182+
183+
upStateFlags := fmt.Sprintf("-state=%s -state-out=%s", afterStatePath, beforeStatePath)
184+
downStateFlags := fmt.Sprintf("-state=%s -state-out=%s", beforeStatePath, afterStatePath)
185+
186+
if err := upDownScript(upMatches, upStateFlags, upFile); err != nil {
187+
return fmt.Errorf("writing the up script: %v", err)
188+
}
189+
if err := upDownScript(downMatches, downStateFlags, downFile); err != nil {
190+
return fmt.Errorf("writing the down script: %v", err)
191+
}
192+
193+
return nil
194+
}
195+
144196
func collectErrors(create *strset.Set, destroy *strset.Set) string {
145197
msg := ""
146198
if create.Size() != 0 {

main.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ func main() {
2323
}
2424

2525
type args struct {
26-
Rename *RenameCmd `arg:"subcommand:rename" help:"rename resources in the same root environment"`
27-
MoveAfter *MoveAfterCmd `arg:"subcommand:move-after" help:"move resources from one root environment to AFTER another"`
28-
Import *ImportCmd `arg:"subcommand:import" help:"import resources generated out-of-band of Terraform"`
29-
Version *struct{} `arg:"subcommand:version" help:"show version"`
26+
Rename *RenameCmd `arg:"subcommand:rename" help:"rename resources in the same root environment"`
27+
MoveAfter *MoveAfterCmd `arg:"subcommand:move-after" help:"move resources from one root environment to AFTER another"`
28+
MoveBefore *MoveBeforeCmd `arg:"subcommand:move-before" help:"move resources from one root environment to BEFORE another"`
29+
Import *ImportCmd `arg:"subcommand:import" help:"import resources generated out-of-band of Terraform"`
30+
Version *struct{} `arg:"subcommand:version" help:"show version"`
3031
}
3132

3233
func (args) Description() string {
@@ -51,6 +52,12 @@ type MoveAfterCmd struct {
5152
After string `arg:"required" help:"the after root directory; will look for AFTER.tfplan and AFTER.tfstate"`
5253
}
5354

55+
type MoveBeforeCmd struct {
56+
Script string `arg:"required" help:"the migration scripts; will generate SCRIPT_up.sh and SCRIPT_down.sh"`
57+
Before string `arg:"required" help:"the before root directory; will look for BEFORE.tfplan and BEFORE.tfstate"`
58+
After string `arg:"required" help:"the after root directory; will look for AFTER.tfstate"`
59+
}
60+
5461
type ImportCmd struct {
5562
UpDown
5663
ResourceDefs string `arg:"--res-defs,required" help:"path to resource definitions"`
@@ -72,6 +79,9 @@ func run() error {
7279
case args.MoveAfter != nil:
7380
cmd := args.MoveAfter
7481
return doMoveAfter(cmd.Script, cmd.Before, cmd.After)
82+
case args.MoveBefore != nil:
83+
cmd := args.MoveBefore
84+
return doMoveBefore(cmd.Script, cmd.Before, cmd.After)
7585
case args.Import != nil:
7686
return doImport(args.Import.Up, args.Import.Down,
7787
args.Import.SrcPlanPath, args.Import.ResourceDefs)

main_test.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,60 @@ func TestRunMoveAfterFailure(t *testing.T) {
175175
}
176176

177177
func TestRunMoveBeforeSuccess(t *testing.T) {
178+
testCases := []struct {
179+
name string
180+
before string
181+
after string // special prefix: dummy
182+
wantScript string
183+
}{
184+
{
185+
name: "happy path simple",
186+
before: "testdata/move-before/01-before",
187+
after: "dummy-01-after",
188+
wantScript: "testdata/move-before/01-want",
189+
},
190+
}
191+
192+
for _, tc := range testCases {
193+
t.Run(tc.name, func(t *testing.T) {
194+
args := []string{"terravalet", "move-before"}
195+
196+
runMoveSuccess(t, args, tc.before, tc.after, tc.wantScript)
197+
})
198+
}
178199
}
179200

180201
func TestRunMoveBeforeFailure(t *testing.T) {
202+
testCases := []struct {
203+
name string
204+
before string // special value: "non-existing"
205+
wantErr string
206+
}{
207+
{
208+
name: "non existing BEFORE plan",
209+
before: "non-existing",
210+
wantErr: "opening the terraform BEFORE plan file: open non-existing.tfplan: no such file or directory",
211+
},
212+
{
213+
name: "BEFORE plan must not contain resources to destroy",
214+
before: "testdata/move-before/02-before",
215+
wantErr: "BEFORE plan contains resources to destroy: [aws_batch_compute_environment.foo_batch]",
216+
},
217+
}
218+
219+
for _, tc := range testCases {
220+
t.Run(tc.name, func(t *testing.T) {
221+
args := []string{"terravalet", "move-before",
222+
"--before=" + tc.before, "--after=testdata/move-before/dummy-after",
223+
}
224+
225+
runMoveFailure(t, args, tc.before, "non-existing", tc.wantErr)
226+
})
227+
}
181228
}
182229

230+
// If after has special prefix "dummy", it will not attempt to copy the
231+
// corresponding tfplan files, to accomodate for move-before.
183232
func runMoveSuccess(t *testing.T, args []string, before, after, wantScript string) {
184233
wantUpPath := wantScript + "_up.sh"
185234
wantUp, err := os.ReadFile(wantUpPath)
@@ -204,9 +253,11 @@ func runMoveSuccess(t *testing.T, args []string, before, after, wantScript strin
204253
filepath.Join(tmpDir, path.Base(before)+".tfplan")); err != nil {
205254
t.Fatal(err)
206255
}
207-
if err := copyfile(after+".tfplan",
208-
filepath.Join(tmpDir, path.Base(after)+".tfplan")); err != nil {
209-
t.Fatal(err)
256+
if !strings.HasPrefix(after, "dummy") {
257+
if err := copyfile(after+".tfplan",
258+
filepath.Join(tmpDir, path.Base(after)+".tfplan")); err != nil {
259+
t.Fatal(err)
260+
}
210261
}
211262

212263
// Change directory to the tmpdir.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
random_pet.prepend: Refreshing state... [id=brave-mole]
2+
3+
Terraform used the selected providers to generate the following execution
4+
plan. Resource actions are indicated with the following symbols:
5+
+ create
6+
7+
Terraform will perform the following actions:
8+
9+
# null_resource.res1 will be created
10+
+ resource "null_resource" "res1" {
11+
+ id = (known after apply)
12+
}
13+
14+
Plan: 1 to add, 0 to change, 0 to destroy.
15+
16+
Changes to Outputs:
17+
+ res1_id = (known after apply)
18+
19+
─────────────────────────────────────────────────────────────────────────────
20+
21+
Note: You didn't use the -out option to save this plan, so Terraform can't
22+
guarantee to take exactly these actions if you run "terraform apply" now.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#! /bin/sh
2+
# DO NOT EDIT. Generated by terravalet.
3+
# terravalet_output_format=2
4+
#
5+
# This script will move 1 items.
6+
7+
set -e
8+
9+
terraform state mv -lock=false -state=01-before.tfstate -state-out=dummy-01-after.tfstate \
10+
'null_resource.res1' \
11+
'null_resource.res1'
12+

testdata/move-before/01-want_up.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#! /bin/sh
2+
# DO NOT EDIT. Generated by terravalet.
3+
# terravalet_output_format=2
4+
#
5+
# This script will move 1 items.
6+
7+
set -e
8+
9+
terraform state mv -lock=false -state=dummy-01-after.tfstate -state-out=01-before.tfstate \
10+
'null_resource.res1' \
11+
'null_resource.res1'
12+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
random_pet.prepend: Refreshing state... [id=brave-mole]
2+
3+
Terraform used the selected providers to generate the following execution
4+
plan. Resource actions are indicated with the following symbols:
5+
+ create
6+
7+
Terraform will perform the following actions:
8+
9+
# null_resource.res1 will be created
10+
+ resource "null_resource" "res1" {
11+
+ id = (known after apply)
12+
}
13+
14+
# aws_batch_compute_environment.foo_batch will be destroyed

0 commit comments

Comments
 (0)