Skip to content

Commit 9d7b5a0

Browse files
committed
Use default statefile and fix dependency breakage
With all the features of Terraform 0.12, the migrate script can completely run off of remote state, so by default the script will let Terraform use its default state, remote or local. There is an option to override this for testing or for keeping remote state safe. Add verbose option to migrate script, which will print the commands being run even if dryrun is not enabled. Revert the output var change in product_services, since it's needed for the dependency graph. However, the old '.*.' operator doesn't work with for_each, so replace it with a new [for] construct.
1 parent 0b2d877 commit 9d7b5a0

File tree

3 files changed

+31
-70
lines changed

3 files changed

+31
-70
lines changed

docs/upgrading_to_project_factory_v4.0.md

+7-53
Original file line numberDiff line numberDiff line change
@@ -66,40 +66,22 @@ chmod +x migrate4.py
6666
terraform init -upgrade
6767
```
6868

69-
### Locally download your Terraform state
69+
### Backup your remote Terraform state
7070

7171
This step is only required if you are using [remote state](https://www.terraform.io/docs/state/remote.html).
7272

7373
```
74-
terraform state pull >> terraform.tfstate
75-
```
76-
77-
You should then disable remote state temporarily:
78-
79-
```hcl
80-
terraform {
81-
# backend "gcs" {
82-
# bucket = "my-bucket-name"
83-
# prefix = "terraform/state/projects"
84-
# }
85-
}
86-
```
87-
88-
After commenting out your remote state configuration, you must re-initialize Terraform.
89-
90-
```
91-
terraform init
74+
terraform state pull > terraform-backup.tfstate
9275
```
9376

9477
### Migrate the Terraform state to match the new Project Factory module structure
9578

9679
```
97-
./migrate4.py terraform.tfstate terraform.tfstate.new
80+
./migrate4.py
9881
```
9982

10083
Expected output:
10184
```txt
102-
cp terraform.tfstate terraform.tfstate.new
10385
---- Migrating the following project-factory modules:
10486
-- module.my-project.module.project-factory
10587
Removed module.my-project.module.project-factory.google_project_service.project_services[0]
@@ -113,13 +95,13 @@ Import successful!
11395
11496
The resources that were imported are shown above. These resources are now in
11597
your Terraform state and will henceforth be managed by Terraform.
116-
State migration complete, verify migration with `terraform plan -state 'terraform.tfstate.new'`
98+
State migration complete, verify migration with `terraform plan`
11799
```
118100

119101
### Check that terraform plans for expected changes
120102

121103
```
122-
terraform plan -state terraform.tfstate.new
104+
terraform plan
123105
```
124106

125107
The project services refactor will show this for each service:
@@ -137,40 +119,12 @@ The project services refactor will show this for each service:
137119
}
138120
```
139121

140-
### Back up the old Terraform state and switch to the new Terraform state
141-
142-
If the Terraform plan suggests no changes, replace the old state file with the new state file.
143-
144-
```
145-
mv terraform.tfstate terraform.tfstate.pre-migrate
146-
mv terraform.tfstate.new terraform.tfstate
147-
```
148-
149122
### Run Terraform to reconcile any differences
150123

151124
```
152125
terraform apply
153126
```
154127

155-
### Re-enable remote state
156-
157-
You should then re-enable remote state:
158-
159-
```hcl
160-
terraform {
161-
backend "gcs" {
162-
bucket = "my-bucket-name"
163-
prefix = "terraform/state/projects"
164-
}
165-
}
166-
```
167-
168-
After restoring remote state, you need to re-initialize Terraform and push your local state to the remote:
169-
170-
```
171-
terraform init -force-copy
172-
```
173-
174128
### Clean up
175129

176130
Once you are done with the migration, you can safely remove `migrate.py`.
@@ -179,10 +133,10 @@ Once you are done with the migration, you can safely remove `migrate.py`.
179133
rm migrate4.py
180134
```
181135

182-
If you are using remote state, you can also remove the local state copies.
136+
If you are using remote state, you can also remove the local state backup.
183137

184138
```
185-
rm -rf terraform.tfstate*
139+
rm -rf terraform-backup.tfstate
186140
```
187141

188142
## Troubleshooting

helpers/migrate4.py

+20-16
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def read_state(statefile):
227227
"""
228228
Read the terraform state at the given path.
229229
"""
230-
argv = ["terraform", "state", "list", "-state", statefile]
230+
argv = ["terraform", "state", "list", *statefile]
231231
result = subprocess.run(argv,
232232
capture_output=True,
233233
check=True,
@@ -242,7 +242,7 @@ def read_resource_value(resource, field, statefile):
242242
Read a specific value from a resource from the terraform state at the
243243
given path.
244244
"""
245-
argv = ["terraform", "state", "show", "-no-color", "-state", statefile, resource]
245+
argv = ["terraform", "state", "show", "-no-color", *statefile, resource]
246246
result = subprocess.run(argv,
247247
capture_output=True,
248248
check=True,
@@ -266,15 +266,15 @@ def state_changes_for_module(module, statefile):
266266
migration = ProjectServicesMigration(module, statefile)
267267

268268
for (old, new, id) in migration.moves():
269-
argv = ["terraform", "state", "rm", "-state", statefile, old]
269+
argv = ["terraform", "state", "rm", *statefile, old]
270270
commands.append(argv)
271-
argv = ["terraform", "import", "-state", statefile, new, id]
271+
argv = ["terraform", "import", *statefile, new, id]
272272
commands.append(argv)
273273

274274
return commands
275275

276276

277-
def migrate(statefile, dryrun=False):
277+
def migrate(statefile, dryrun=False, verbose=False):
278278
"""
279279
Migrate the terraform state in `statefile` to match the post-refactor
280280
resource structure.
@@ -310,34 +310,38 @@ def migrate(statefile, dryrun=False):
310310
commands += state_changes_for_module(factory, statefile)
311311

312312
for argv in commands:
313-
if dryrun:
313+
if dryrun or verbose:
314314
print(" ".join(argv))
315-
else:
315+
if not dryrun:
316316
subprocess.run(argv, check=True, encoding='utf-8')
317317

318318

319319
def main(argv):
320320
parser = argparser()
321321
args = parser.parse_args(argv[1:])
322322

323-
print("cp {} {}".format(args.oldstate, args.newstate))
324-
shutil.copy(args.oldstate, args.newstate)
323+
state = []
324+
plan_command = "terraform plan"
325+
if args.state:
326+
state = ["-state", args.state]
327+
plan_command += " {}".format(' '.join(state))
325328

326-
migrate(args.newstate, dryrun=args.dryrun)
329+
migrate(state, dryrun=args.dryrun, verbose=args.verbose)
327330
print("State migration complete, verify migration with "
328-
"`terraform plan -state '{}'`".format(args.newstate))
331+
"`{}`".format(plan_command))
329332

330333

331334
def argparser():
332335
parser = argparse.ArgumentParser(description='Migrate Terraform state')
333-
parser.add_argument('oldstate', metavar='oldstate.json',
334-
help='The current Terraform state (will not be '
335-
'modified)')
336-
parser.add_argument('newstate', metavar='newstate.json',
337-
help='The path to the new state file')
336+
parser.add_argument('--state',
337+
help='The Terraform state file to modify, otherwise '
338+
'Terraform default')
338339
parser.add_argument('--dryrun', action='store_true',
339340
help='Print the `terraform state mv` commands instead '
340341
'of running the commands.')
342+
parser.add_argument('--verbose', '-v', action='store_true',
343+
help='Print the `terraform state mv` commands that '
344+
'are run.')
341345
return parser
342346

343347

modules/project_services/outputs.tf

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
output "project_id" {
1818
description = "The GCP project you want to enable APIs on"
19-
value = var.enable_apis && length(var.activate_apis) > 0 ? var.project_id : ""
19+
value = element(
20+
[for v in google_project_service.project_services : v.project],
21+
0,
22+
)
2023
}
2124

0 commit comments

Comments
 (0)