dmlittle/scenery

Add support for terragrunt plan

antonbabenko opened this issue · 6 comments

The output is nicer with scenery, but all json changes are still on one line.

@antonbabenko could you provide an example of a terraform plan output that display JSON changes in a single line? It should be pretty-printing. Here's an example using a dummy AWS IAM Policy:

Before

> terraform plan 
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.aws_iam_policy_document.example: Refreshing state...

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

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:

  + aws_iam_policy.example
      id:     <computed>
      arn:    <computed>
      name:   "example_policy"
      path:   "/"
      policy: "{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"1\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"s3:ListAllMyBuckets\",\n        \"s3:GetBucketLocation\"\n      ],\n      \"Resource\": \"arn:aws:s3:::*\"\n    },\n    {\n      \"Sid\": \"\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"s3:ListBucket\",\n      \"Resource\": \"arn:aws:s3:::bucket-example\",\n      \"Condition\": {\n        \"StringLike\":{\n          \"s3:prefix\": [\n            \"home/\",\n            \"\"\n          ]\n        }\n      }\n    },\n    {\n      \"Sid\": \"\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"s3:*\",\n      \"Resource\": [\n        \"arn:aws:s3:::bucket-example/home/*\",\n        \"arn:aws:s3:::bucket-example/home\"\n      ]\n    }\n  ]\n}"


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

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

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

After

> terraform plan | scenery
+ aws_iam_policy.example
    id:     <computed>
    arn:    <computed>
    name:   "example_policy"
    path:   "/"
    policy: "{
               "Statement": [
                 {
                   "Action": [
                     "s3:ListAllMyBuckets",
                     "s3:GetBucketLocation"
                   ],
                   "Effect": "Allow",
                   "Resource": "arn:aws:s3:::*",
                   "Sid": "1"
                 },
                 {
                   "Action": "s3:ListBucket",
                   "Condition": {
                     "StringLike": {
                       "s3:prefix": [
                         "home/",
                         ""
                       ]
                     }
                   },
                   "Effect": "Allow",
                   "Resource": "arn:aws:s3:::bucket-example",
                   "Sid": ""
                 },
                 {
                   "Action": "s3:*",
                   "Effect": "Allow",
                   "Resource": [
                     "arn:aws:s3:::bucket-example/home/*",
                     "arn:aws:s3:::bucket-example/home"
                   ],
                   "Sid": ""
                 }
               ],
               "Version": "2012-10-17"
             }"

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

I think I misunderstood your original request. I don't use terragrunt but I'm happy to add support for it! Would you be able to provide some sample terragrunt plan outputs? It's ok if you redact information, just the general structure of the output should be fine.

Terraform output is fine. Let me find some terragrunt output now.

This is pretty much real output from terragrunt apply | scenery:

<= data.aws_ecs_task_definition.this
    id:              <computed>
    family:          <computed>
    network_mode:    <computed>
    revision:        <computed>
    status:          <computed>
    task_definition: "api"
    task_role_arn:   <computed>

~ aws_ecs_service.this
    task_definition: "api:57" => "${data.aws_ecs_task_definition.this.family}:${max(aws_ecs_task_definition.this.revision, data.aws_ecs_task_definition.this.revision)}"

-/+ aws_ecs_task_definition.this (new resource required)
    id:                                  "api" => <computed> (forces new resource)
    arn:                                 "arn:aws:ecs:eu-central-1:998506250321:task-definition/api:58" => <computed>
    container_definitions:               "[{"cpu":0,"environment":[{"name":"MYSQL_DATABASE","value":" sample_staging"},{"name":"RAILS_ENV","value":"staging"},{"name":"MYSQL_PORT","value":"3306"},{"name":"MYSQL_USER","value":" sample"},{"name":"CHAMBER_SERVICES","value":"api-staging"},{"name":"MYSQL_HOST","value":"staging.cluster-e3q0bvkskycc.eu-central-1.rds.amazonaws.com"},{"name":"REDIS_URL","value":"redis://master.staging.ghdeld.euc1.cache.amazonaws.com:6379"}],"essential":true,"image":"132673857922.dkr.ecr.eu-central-1.amazonaws.com/ sample-api:v5","logConfiguration":{"logDriver":"awslogs","options":{"awslogs-group":"app","awslogs-region":"eu-central-1","awslogs-stream-prefix":"ecs"}},"memory":512,"memoryReservation":256,"mountPoints":[],"name":"api","portMappings":[{"containerPort":80,"hostPort":80,"protocol":"tcp"}],"readonlyRootFilesystem":false,"secrets":[{"name":"MYSQL_PASSWORD","valueFrom":"/rds/staging/db_password"}],"volumesFrom":[]},{"cpu":0,"environment":[{"name":"MYSQL_DATABASE","value":" sample_staging"},{"name":"RAILS_ENV","value":"staging"},{"name":"MYSQL_PORT","value":"3306"},{"name":"MYSQL_USER","value":" sample"},{"name":"CHAMBER_SERVICES","value":"api-staging"},{"name":"RUN_DELAYED_JOB","value":"true"},{"name":"MYSQL_HOST","value":"staging.cluster-e3q0bvkskycc.eu-central-1.rds.amazonaws.com"},{"name":"REDIS_URL","value":"redis://master.staging.ghdeld.euc1.cache.amazonaws.com:6379"}],"essential":true,"healthCheck":{"command":["/bin/true"],"interval":30,"retries":3,"startPeriod":20,"timeout":5},"image":"132673857922.dkr.ecr.eu-central-1.amazonaws.com/ sample-api:v5","logConfiguration":{"logDriver":"awslogs","options":{"awslogs-group":"app","awslogs-region":"eu-central-1","awslogs-stream-prefix":"ecs"}},"memory":256,"memoryReservation":256,"mountPoints":[],"name":"delayed-job","portMappings":[{"containerPort":81,"hostPort":81,"protocol":"tcp"}],"readonlyRootFilesystem":false,"secrets":[{"name":"MYSQL_PASSWORD","valueFrom":"/rds/staging/db_password"}],"volumesFrom":[]}]" => "[{"command":null,"cpu":0,"entryPoint":null,"environment":[{"name":"RAILS_ENV","value":"staging"},{"name":"CHAMBER_SERVICES","value":"api-staging"},{"name":"MYSQL_HOST","value":"staging.cluster-e3q0bvkskycc.eu-central-1.rds.amazonaws.com"},{"name":"MYSQL_PORT","value":"3306"},{"name":"MYSQL_USER","value":" sample"},{"name":"MYSQL_DATABASE","value":" sample_staging"},{"name":"REDIS_URL","value":"redis://master.staging.ghdeld.euc1.cache.amazonaws.com:6379"}],"essential":true,"healthCheck":null,"image":"132673857922.dkr.ecr.eu-central-1.amazonaws.com/ sample-api:v5","logConfiguration":{"logDriver":"awslogs","options":{"awslogs-group":"app","awslogs-region":"eu-central-1","awslogs-stream-prefix":"ecs"}},"memory":512,"memoryReservation":256,"mountPoints":null,"name":"api","portMappings":[{"containerPort":80,"hostPort":80,"protocol":"tcp"}],"readonlyRootFilesystem":false,"secrets":[{"name":"MYSQL_PASSWORD","valueFrom":"/rds/staging/db_password"}],"workingDirectory":null},{"command":null,"cpu":0,"entryPoint":null,"environment":[{"name":"RAILS_ENV","value":"staging"},{"name":"CHAMBER_SERVICES","value":"api-staging"},{"name":"MYSQL_HOST","value":"staging.cluster-e3q0bvkskycc.eu-central-1.rds.amazonaws.com"},{"name":"MYSQL_PORT","value":"3306"},{"name":"MYSQL_USER","value":" sample"},{"name":"MYSQL_DATABASE","value":" sample_staging"},{"name":"REDIS_URL","value":"redis://master.staging.ghdeld.euc1.cache.amazonaws.com:6379"},{"name":"RUN_DELAYED_JOB","value":"true"}],"essential":false,"healthCheck":{"command":["/bin/true"],"startPeriod":20},"image":"132673857922.dkr.ecr.eu-central-1.amazonaws.com/ sample-api:v5","logConfiguration":{"logDriver":"awslogs","options":{"awslogs-group":"app","awslogs-region":"eu-central-1","awslogs-stream-prefix":"ecs"}},"memory":256,"memoryReservation":256,"mountPoints":null,"name":"delayed-job","portMappings":[{"containerPort":81,"hostPort":81,"protocol":"tcp"}],"readonlyRootFilesystem":false,"secrets":[{"name":"MYSQL_PASSWORD","valueFrom":"/rds/staging/db_password"}],"workingDirectory":null}]" (forces new resource)
    cpu:                                 "1024" => "1024"
    execution_role_arn:                  "arn:aws:iam::998506250321:role/api-ecs-task-execution-20181227160008349900000001" => "arn:aws:iam::998506250321:role/api-ecs-task-execution-20181227160008349900000001"
    family:                              "api" => "api"
    memory:                              "2048" => "2048"
    network_mode:                        "awsvpc" => "awsvpc"
    requires_compatibilities.#:          "1" => "1"
    requires_compatibilities.2737437151: "EC2" => "EC2"
    revision:                            "58" => <computed>
    task_role_arn:                       "arn:aws:iam::998506250321:role/api-ecs-task-execution-20181227160008349900000001" => "arn:aws:iam::998506250321:role/api-ecs-task-execution-20181227160008349900000001"

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

Another bug when using terragrunt with scenery is that scenery jumps in action a bit too early and does not let terragrunt to show the plan and wait for input.

[terragrunt] 2019/01/15 19:36:42 Running command: terraform apply -var-file=common.tfvars -var-file=terraform.tfvars -input=false
<<<<<<<<< Expect input here >>>>>>>>>>>

Error: Apply cancelled.


[terragrunt] 2019/01/15 19:36:52 Detected 2 Hooks
[terragrunt] 2019/01/15 19:36:52 Hit multiple errors:
exit status 1
<= data.aws_ecs_task_definition.this

# complete output is above

I've just tried terragrunt apply | landscape it is waiting correctly. Not sure if it helps you to debug.

@antonbabenko a quick update regarding supporting terragrunt.

Currently, we're failing to parse the terragrunt output because some values are not properly escaped. In the example provided the values of the container_definitions array are not escaped. Ideally terragrunt outputs properly escaped values but I'm guessing the reason why they don't is because the output isn't really meant to be machine readable and escaping it makes it harder for humans read.

Long term I can see two possible solutions: 1) we can do some preprocessing of the input to find non-escaped strings and escape them accordingly or 2) created a custom lexer that is able to parse non-properly escaped characters easily.

I've also created a separate issue (#11) to track scenery jumping into action early.

Scenery is not actively maintained and the repo will be archived momentarily. I no longer have the time to maintain this tool nor do I think it should be kept being used as Terraform 0.11 has been deprecated for over a year now (scenery can only parse Terraform 0.11 plan outputs). Terraform 0.14 has some plan output changes as well as introduced the concept of concise diff plan outputs which does most of what scenery currently does.

If you'd like to add new functionality as you cannot upgrade your terraform version feel free you fork the repo.