Using tf plan file

Hi,
I need to thank you for this piece of software, which saves my time a lot. Also it makes me a little headaches as I’m pretty new to TF at all and with combination with TS (and Ruby) it’s fun sometimes :slight_smile:

I’m spinning up new Azure infrastructure for our testing clusters, using Gitlab-ci. I tried to use all up, buit I’m not able to specify plan & json files for every stack (also I’m missing --auto parameter).

So I’m trying to use plan file for apply/up, but it seems, TS is ignoring it… So I want to just speed up our pipeline, but:

$ terraspace plan ref --out ref.tfplan --auto
Building .terraspace-cache/eastus/dev/stacks/ref
Built in .terraspace-cache/eastus/dev/stacks/ref
Current directory: .terraspace-cache/eastus/dev/stacks/ref
=> terraform plan -input=false -out /home/user/terraform/terraspace-azure/ref.tfplan

when I run apply/up, it runs init again init …

$ terraspace up ref --plan=ref.tfplan --auto --yes
Building .terraspace-cache/eastus/dev/stacks/ref
Built in .terraspace-cache/eastus/dev/stacks/ref
Current directory: .terraspace-cache/eastus/dev/stacks/ref
=> terraform plan -input=false -out /tmp/terraspace/plans/ref-20210211134546.plan
Acquiring state lock. This may take a few moments...
...
...
...
=> terraform apply -auto-approve -input=false /tmp/terraspace/plans/ref-20210211134546.plan
Acquiring state lock. This may take a few moments...

Do you have some advice how to fix that? In short I need to plan changes in dev branches, and if it pass well, merge into master & apply (using the same plan).

Or any other advice to what I should focus using gitlab please?

Thank you
Jan V.

RE: plan file not being used

Took a look. Here’s a debugging session.

Debugging Session Showing Plan File Being Used

Check setup:

$ terraspace check_setup
Detected Terrspace version: 0.5.10
Detected Terraform bin: /home/ec2-user/.tfenv/bin/terraform
Detected Terraform v0.14.4
Terraspace requires Terraform v0.12.x and above
You're all set!
$

Generate terraspace project for testing:

$ terraspace new project infra --plugin azurerm --examples
=> Creating new project called infra.
      create  infra
      create  infra/.gitignore
      create  infra/Gemfile
      create  infra/README.md
      create  infra/Terrafile
      create  infra/config/app.rb
       exist  infra
      create  infra/config/terraform/backend.tf
      create  infra/config/terraform/provider.tf
=> Creating test for new module: example
      create  infra/app/modules/example
      create  infra/app/modules/example/main.tf
      create  infra/app/modules/example/outputs.tf
      create  infra/app/modules/example/variables.tf
=> Creating new stack called demo.
      create  infra/app/stacks/demo
      create  infra/app/stacks/demo/main.tf
      create  infra/app/stacks/demo/outputs.tf
      create  infra/app/stacks/demo/variables.tf
=> Installing dependencies with: bundle install
Fetching gem metadata from https://rubygems.org/........
Resolving dependencies........
Using rake 13.0.3
Using concurrent-ruby 1.1.8
Using zeitwerk 2.4.2
Using public_suffix 4.0.6
Using aws-eventstream 1.1.0
Using aws-partitions 1.426.0
Using jmespath 1.4.0
Using memoist 0.16.2
Using faraday-net_http 1.0.1
Using multipart-post 2.1.1
Using ruby2_keywords 0.0.4
Using connection_pool 2.2.3
Using racc 1.5.2
Using azure_info 0.1.1
Using unf_ext 0.0.7.7
Using timeliness 0.3.10
Using bundler 2.2.5
Using text-table 1.2.4
Using declarative 0.0.20
Using declarative-option 0.1.0
Using deep_merge 1.2.1
Using diff-lcs 1.4.4
Using digest-crc 0.6.3
Using rainbow 3.0.0
Using eventmachine 1.2.7
Using google-protobuf 3.14.0 (x86_64-linux)
Using jwt 2.2.2
Using multi_json 1.15.0
Using os 1.1.1
Using httpclient 2.8.3
Using mini_mime 1.0.2
Using uber 0.1.0
Using retriable 3.1.2
Using rexml 3.2.4
Using webrick 1.7.0
Using google-cloud-errors 1.0.1
Using graph 2.10.0
Using tilt 2.0.10
Using rspec-support 3.10.2
Using thor 1.1.0
Using tty-tree 0.4.0
Using i18n 1.8.8
Using tzinfo 2.0.4
Using addressable 2.7.0
Using aws-sigv4 1.2.2
Using gcp_data 0.2.0
Using faraday 1.3.0
Using net-http-persistent 4.0.1
Using nokogiri 1.11.1 (x86_64-linux)
Using unf 0.1.4
Using rhcl 0.1.0
Using dsl_evaluator 0.1.3
Using eventmachine-tail 0.6.5
Using googleapis-common-protos-types 1.0.6
Using representable 3.0.4
Using rspec-core 3.10.1
Using rspec-expectations 3.10.1
Using rspec-mocks 3.10.2
Using aws-sdk-core 3.112.0
Using faraday_middleware 1.0.0
Using ms_rest 0.7.6
Using signet 0.14.1
Using google-cloud-env 1.4.0
Using domain_name 0.5.20190701
Using hcl_parser 0.1.0
Using grpc 1.35.0 (x86_64-linux)
Using rspec 3.10.0
Using aws-sdk-dynamodb 1.59.0
Using aws-sdk-kms 1.42.0
Using aws-sdk-secretsmanager 1.44.0
Using aws-sdk-ssm 1.104.0
Using aws_data 0.1.1
Using azure-storage-common 2.0.2
Using googleauth 0.15.1
Using google-cloud-core 1.5.0
Using http-cookie 1.0.3
Using googleapis-common-protos 1.3.11
Using aws-sdk-s3 1.88.0
Using azure-storage-blob 2.0.1
Using google-apis-core 0.2.1
Using faraday-cookie_jar 0.0.7
Using gapic-common 0.3.4
Using grpc-google-iam-v1 0.6.11
Using google-apis-iamcredentials_v1 0.1.0
Using google-apis-storage_v1 0.2.0
Using ms_rest_azure 0.12.0
Using google-cloud-secret_manager-v1 0.7.0
Using google-cloud-secret_manager-v1beta1 0.7.0
Using google-cloud-storage 1.30.0
Using azure_mgmt_resources 0.18.1
Using azure_mgmt_storage 0.22.0
Using google-cloud-secret_manager 1.0.2
Using terraspace_plugin_azurerm 0.3.0
Using terraspace_plugin_google 0.3.0
Using minitest 5.14.3
Using activesupport 6.1.2.1
Using cli-format 0.2.0
Using rspec-terraspace 0.2.2
Using terraspace-bundler 0.3.2
Using s3-secure 0.5.1
Using render_me_pretty 0.8.3
Using terraspace_plugin_aws 0.3.0
Using terraspace 0.5.10
Bundle complete! 3 Gemfile dependencies, 103 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
================================================================
Congrats! You have successfully created a terraspace project.
Check out the created files. Adjust to the examples and then deploy with:

    cd infra
    terraspace up demo -y   # to deploy
    terraspace down demo -y # to destroy

More info: https://terraspace.cloud/
$ cd infra
$ terraspace up demo -y
Building .terraspace-cache/eastus/dev/stacks/demo
Built in .terraspace-cache/eastus/dev/stacks/demo
Current directory: .terraspace-cache/eastus/dev/stacks/demo
=> terraform init -get -input=false >> /tmp/terraspace/log/init/demo.log
=> terraform plan -input=false -out /tmp/terraspace/plans/demo-5f4f02d7582258d83fc793f91f3f6da9.plan
Acquiring state lock. This may take a few moments...

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_resource_group.this will be created
  + resource "azurerm_resource_group" "this" {
      + id       = (known after apply)
      + location = "eastus"
      + name     = (known after apply)
    }

  # random_pet.this will be created
  + resource "random_pet" "this" {
      + id     = (known after apply)
      + length = 2
    }

  # module.storage_account.azurerm_storage_account.this will be created
  + resource "azurerm_storage_account" "this" {
      + access_tier                      = (known after apply)
      + account_kind                     = "StorageV2"
      + account_replication_type         = "GRS"
      + account_tier                     = "Standard"
      + allow_blob_public_access         = false
      + enable_https_traffic_only        = true
      + id                               = (known after apply)
      + is_hns_enabled                   = false
      + large_file_share_enabled         = (known after apply)
      + location                         = "eastus"
      + min_tls_version                  = "TLS1_0"
      + name                             = (known after apply)
      + primary_access_key               = (sensitive value)
      + primary_blob_connection_string   = (sensitive value)
      + primary_blob_endpoint            = (known after apply)
      + primary_blob_host                = (known after apply)
      + primary_connection_string        = (sensitive value)
      + primary_dfs_endpoint             = (known after apply)
      + primary_dfs_host                 = (known after apply)
      + primary_file_endpoint            = (known after apply)
      + primary_file_host                = (known after apply)
      + primary_location                 = (known after apply)
      + primary_queue_endpoint           = (known after apply)
      + primary_queue_host               = (known after apply)
      + primary_table_endpoint           = (known after apply)
      + primary_table_host               = (known after apply)
      + primary_web_endpoint             = (known after apply)
      + primary_web_host                 = (known after apply)
      + resource_group_name              = (known after apply)
      + secondary_access_key             = (sensitive value)
      + secondary_blob_connection_string = (sensitive value)
      + secondary_blob_endpoint          = (known after apply)
      + secondary_blob_host              = (known after apply)
      + secondary_connection_string      = (sensitive value)
      + secondary_dfs_endpoint           = (known after apply)
      + secondary_dfs_host               = (known after apply)
      + secondary_file_endpoint          = (known after apply)
      + secondary_file_host              = (known after apply)
      + secondary_location               = (known after apply)
      + secondary_queue_endpoint         = (known after apply)
      + secondary_queue_host             = (known after apply)
      + secondary_table_endpoint         = (known after apply)
      + secondary_table_host             = (known after apply)
      + secondary_web_endpoint           = (known after apply)
      + secondary_web_host               = (known after apply)

      + blob_properties {
          + cors_rule {
              + allowed_headers    = (known after apply)
              + allowed_methods    = (known after apply)
              + allowed_origins    = (known after apply)
              + exposed_headers    = (known after apply)
              + max_age_in_seconds = (known after apply)
            }

          + delete_retention_policy {
              + days = (known after apply)
            }
        }

      + identity {
          + principal_id = (known after apply)
          + tenant_id    = (known after apply)
          + type         = (known after apply)
        }

      + network_rules {
          + bypass                     = (known after apply)
          + default_action             = (known after apply)
          + ip_rules                   = (known after apply)
          + virtual_network_subnet_ids = (known after apply)
        }

      + queue_properties {
          + cors_rule {
              + allowed_headers    = (known after apply)
              + allowed_methods    = (known after apply)
              + allowed_origins    = (known after apply)
              + exposed_headers    = (known after apply)
              + max_age_in_seconds = (known after apply)
            }

          + hour_metrics {
              + enabled               = (known after apply)
              + include_apis          = (known after apply)
              + retention_policy_days = (known after apply)
              + version               = (known after apply)
            }

          + logging {
              + delete                = (known after apply)
              + read                  = (known after apply)
              + retention_policy_days = (known after apply)
              + version               = (known after apply)
              + write                 = (known after apply)
            }

          + minute_metrics {
              + enabled               = (known after apply)
              + include_apis          = (known after apply)
              + retention_policy_days = (known after apply)
              + version               = (known after apply)
            }
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + storage_account_id = (known after apply)

------------------------------------------------------------------------

This plan was saved to: /tmp/terraspace/plans/demo-5f4f02d7582258d83fc793f91f3f6da9.plan

To perform exactly these actions, run the following command to apply:
    terraform apply "/tmp/terraspace/plans/demo-5f4f02d7582258d83fc793f91f3f6da9.plan"

=> terraform apply -auto-approve -input=false /tmp/terraspace/plans/demo-5f4f02d7582258d83fc793f91f3f6da9.plan
Acquiring state lock. This may take a few moments...
random_pet.this: Creating...
random_pet.this: Creation complete after 0s [id=suregecko]
azurerm_resource_group.this: Creating...
azurerm_resource_group.this: Creation complete after 1s [id=/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko]
module.storage_account.azurerm_storage_account.this: Creating...
module.storage_account.azurerm_storage_account.this: Still creating... [10s elapsed]
module.storage_account.azurerm_storage_account.this: Still creating... [20s elapsed]
module.storage_account.azurerm_storage_account.this: Creation complete after 23s [id=/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko/providers/Microsoft.Storage/storageAccounts/sasuregecko]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

storage_account_id = "/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko/providers/Microsoft.Storage/storageAccounts/sasuregecko"
Time took: 32s

Seed tfvars files to be used to make a change and a plan file.

$ terraspace seed demo
Seeding tfvar files for demo
Building .terraspace-cache/eastus/dev/stacks/demo
Built in .terraspace-cache/eastus/dev/stacks/demo
Reading: .terraspace-cache/eastus/dev/stacks/demo/variables.tf
      create  app/stacks/demo/tfvars/dev.tfvars
$ c9 app/stacks/demo/tfvars/dev.tfvars
$ cat app/stacks/demo/tfvars/dev.tfvars
# Optional variables:
enable_https_traffic_only = false
$ cat app/stacks/demo/tfvars/dev.tfvars
# Optional variables:
enable_https_traffic_only = false # changed from true to false to test

Create a plan file:

$ terraspace plan demo --out plan.save
Building .terraspace-cache/eastus/dev/stacks/demo
Built in .terraspace-cache/eastus/dev/stacks/demo
Current directory: .terraspace-cache/eastus/dev/stacks/demo
=> terraform plan -input=false -out /home/ec2-user/environment/infra/plan.save
Acquiring state lock. This may take a few moments...
random_pet.this: Refreshing state... [id=suregecko]
azurerm_resource_group.this: Refreshing state... [id=/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko]
module.storage_account.azurerm_storage_account.this: Refreshing state... [id=/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko/providers/Microsoft.Storage/storageAccounts/sasuregecko]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # module.storage_account.azurerm_storage_account.this will be updated in-place
  ~ resource "azurerm_storage_account" "this" {
      ~ enable_https_traffic_only      = true -> false
        id                             = "/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko/providers/Microsoft.Storage/storageAccounts/sasuregecko"
        name                           = "sasuregecko"
        tags                           = {}
        # (28 unchanged attributes hidden)


        # (2 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

------------------------------------------------------------------------

This plan was saved to: /home/ec2-user/environment/infra/plan.save

To perform exactly these actions, run the following command to apply:
    terraform apply "/home/ec2-user/environment/infra/plan.save"

Use the plan file

$ terraspace up demo --plan plan.save
Building .terraspace-cache/eastus/dev/stacks/demo
Built in .terraspace-cache/eastus/dev/stacks/demo
Current directory: .terraspace-cache/eastus/dev/stacks/demo
=> terraform apply -input=false /home/ec2-user/environment/infra/.terraspace-cache/eastus/dev/stacks/demo/plan.save
Acquiring state lock. This may take a few moments...
module.storage_account.azurerm_storage_account.this: Modifying... [id=/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko/providers/Microsoft.Storage/storageAccounts/sasuregecko]
module.storage_account.azurerm_storage_account.this: Modifications complete after 5s [id=/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko/providers/Microsoft.Storage/storageAccounts/sasuregecko]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Outputs:

storage_account_id = "/subscriptions/61bd6788-c44e-4677-bf0e-6c380785c8a8/resourceGroups/demo-resources-suregecko/providers/Microsoft.Storage/storageAccounts/sasuregecko"
Time took: 13s

So it looks like it’s respecting the plan.save

terraform apply -input=false /home/ec2-user/environment/infra/.terraspace-cache/eastus/dev/stacks/demo/plan.save

In the debugging session the plan file is being used.

Found out the difference. The difference is the --yes option. There’s a bug where when the --yes option is used, a new plan is always generated. Fixed the bug in:

Upgrade to v0.5.11 to remove this bug. Also note, when terraform receives a plan file it does not prompt. This is terraform, not terraspace, behavior. So the --yes option is not actually required.

RE: terraspace all and plans

Bummer. Terraspace all does not handle plan files for multiple stacks yet. Have some ideas to address this, will have to dig into this and will take some time.

Hello,
thx for quick response. I’ve changed pipeline (removed --yes) and plan file is used.

Thx a lot.
Regards
Jan