mercari / tfnotify

A CLI command to parse Terraform execution result and notify it to GitHub
MIT License
620 stars 91 forks source link

Long plan output gives "cannot parse plan result" #40

Closed Starefossen closed 4 years ago

Starefossen commented 5 years ago

WHAT

We have a very long Terraform Plan output with a lot of inline JSON that cuauses the following error:

$ terraform plan terraform-prod.plan | tfnotify plan 

The plan command received a saved plan file as input. This command
will output the saved plan. This will not modify the already-existing
plan. If you wish to generate a new plan, please pass in a configuration
directory as an argument.

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

Terraform will perform the following actions:

  ~ module.apps.helm_release.prometheus-operator
      values.0:                     "global:\n  rbac:\n    enabled: true\ncommonLabels:\n  prometheus: default\ndefaultRules:\n  labels:\n    alertmanager: default\n  rules:\n    alertmanager: true\n    etcd: false\n    general: true\n    k8s: true\n    kubeApiserver: false\n    kubePrometheusNodeAlerting: true\n    kubePrometheusNodeRecording: true\n    kubeScheduler: false\n
…
(removed looooooooong output here)
…
eShift\": null,\n      \"title\": \"NATS Pending: Account Created\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n  \"refresh\": \"5s\",\n  \"schemaVersion\": 18,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": []\n  },\n  \"time\": {\n    \"from\": \"now-1h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Onboarding\",\n  \"uid\": \"0_R1z5MWk\",\n  \"version\": 6\n}"
cannot parse plan result
Exited with code 1

From the looks of it tfnotify just gives up half way through and can not find what it is looking for in order to classify this as a valid plan.

$ terraform plan terraform-test.plan | wc
      86   20863  325582

As you can see the plan is quit big, this would definitly hit the GitHub comment limit #34, but this should still not fail in tfnotify, don't you think?

This is the latest version of tfnotify and the latest v0.11 version of Terraform.

timothegenzmer commented 4 years ago

We are currently facing the same issue. @Starefossen were you able to resolve the issue?

timothegenzmer commented 4 years ago

the issue is in tee.go the issue is that tfnotify scans by line and s.Scan() panics when the line is too long

Scan panics if the split function returns too many empty tokens without advancing the input. This is a common error mode for scanners.

This is a fixed version that scans by byte.

package main

import (
    "bufio"
    "bytes"
    "io"

    "github.com/mattn/go-colorable"
)

func tee(stdin io.Reader, stdout io.Writer) string {
    var b1 bytes.Buffer
    var b2 bytes.Buffer

    tee := io.TeeReader(stdin, &b1)
    s := bufio.NewScanner(tee)
    s.Split(bufio.ScanBytes)
    for s.Scan() {
        stdout.Write(s.Bytes())
    }

    uncolorize := colorable.NewNonColorable(&b2)
    uncolorize.Write(b1.Bytes())

    return b2.String()
}