Skip to content

Commit

Permalink
Implements the graph command (#257)
Browse files Browse the repository at this point in the history
* Implements the graph command

* Adds version gating, additional tests
  • Loading branch information
Chrisell authored Dec 14, 2021
1 parent 8e82359 commit 38e1272
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 0 deletions.
85 changes: 85 additions & 0 deletions tfexec/graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package tfexec

import (
"context"
"fmt"
"os/exec"
"strings"
)

type graphConfig struct {
plan string
drawCycles bool
graphType string
}

var defaultGraphOptions = graphConfig{}

type GraphOption interface {
configureGraph(*graphConfig)
}

func (opt *GraphPlanOption) configureGraph(conf *graphConfig) {
conf.plan = opt.file
}

func (opt *DrawCyclesOption) configureGraph(conf *graphConfig) {
conf.drawCycles = opt.drawCycles
}

func (opt *GraphTypeOption) configureGraph(conf *graphConfig) {
conf.graphType = opt.graphType
}

func (tf *Terraform) Graph(ctx context.Context, opts ...GraphOption) (string, error) {
graphCmd, err := tf.graphCmd(ctx, opts...)
if err != nil {
return "", err
}
var outBuf strings.Builder
graphCmd.Stdout = &outBuf
err = tf.runTerraformCmd(ctx, graphCmd)
if err != nil {
return "", err
}

return outBuf.String(), nil

}

func (tf *Terraform) graphCmd(ctx context.Context, opts ...GraphOption) (*exec.Cmd, error) {
c := defaultGraphOptions

for _, o := range opts {
o.configureGraph(&c)
}

args := []string{"graph"}

if c.plan != "" {
// plan was a positional arguement prior to Terraform 0.15.0. Ensure proper use by checking version.
if err := tf.compatible(ctx, tf0_15_0, nil); err == nil {
args = append(args, "-plan="+c.plan)
} else {
args = append(args, c.plan)
}
}

if c.drawCycles {
err := tf.compatible(ctx, tf0_5_0, nil)
if err != nil {
return nil, fmt.Errorf("-draw-cycles was first introduced in Terraform 0.5.0: %w", err)
}
args = append(args, "-draw-cycles")
}

if c.graphType != "" {
err := tf.compatible(ctx, tf0_8_0, nil)
if err != nil {
return nil, fmt.Errorf("-graph-type was first introduced in Terraform 0.8.0: %w", err)
}
args = append(args, "-type="+c.graphType)
}

return tf.buildTerraformCmd(ctx, nil, args...), nil
}
76 changes: 76 additions & 0 deletions tfexec/graph_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package tfexec

import (
"context"
"testing"

"github.com/hashicorp/terraform-exec/tfexec/internal/testutil"
)

func TestGraphCmd(t *testing.T) {
td := t.TempDir()

tf, err := NewTerraform(td, tfVersion(t, testutil.Latest013))
if err != nil {
t.Fatal(err)
}

// empty env, to avoid environ mismatch in testing
tf.SetEnv(map[string]string{})

t.Run("defaults", func(t *testing.T) {
graphCmd, _ := tf.graphCmd(context.Background())

assertCmd(t, []string{
"graph",
}, nil, graphCmd)
})

t.Run("override all defaults", func(t *testing.T) {
graphCmd, _ := tf.graphCmd(context.Background(),
GraphPlan("teststate"),
DrawCycles(true),
GraphType("output"))

assertCmd(t, []string{
"graph",
"teststate",
"-draw-cycles",
"-type=output",
}, nil, graphCmd)
})
}

func TestGraphCmd15(t *testing.T) {
td := t.TempDir()

tf, err := NewTerraform(td, tfVersion(t, testutil.Latest015))
if err != nil {
t.Fatal(err)
}

// empty env, to avoid environ mismatch in testing
tf.SetEnv(map[string]string{})

t.Run("defaults", func(t *testing.T) {
graphCmd, _ := tf.graphCmd(context.Background())

assertCmd(t, []string{
"graph",
}, nil, graphCmd)
})

t.Run("override all defaults", func(t *testing.T) {
graphCmd, _ := tf.graphCmd(context.Background(),
GraphPlan("teststate"),
DrawCycles(true),
GraphType("output"))

assertCmd(t, []string{
"graph",
"-plan=teststate",
"-draw-cycles",
"-type=output",
}, nil, graphCmd)
})
}
35 changes: 35 additions & 0 deletions tfexec/internal/e2etest/graph_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package e2etest

import (
"context"
"strings"
"testing"

"github.com/hashicorp/go-version"

"github.com/hashicorp/terraform-exec/tfexec"
)

func TestGraph(t *testing.T) {
runTest(t, "basic", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) {
err := tf.Init(context.Background())
if err != nil {
t.Fatalf("error running Init in test directory: %s", err)
}

err = tf.Apply(context.Background())
if err != nil {
t.Fatalf("error running Apply: %s", err)
}

graphOutput, err := tf.Graph(context.Background())
if err != nil {
t.Fatalf("error running Graph: %s", err)
}

// Graph output differs slightly between versions, but resource subgraph remains consistent
if !strings.Contains(graphOutput, `"[root] null_resource.foo" [label = "null_resource.foo", shape = "box"]`) {
t.Fatalf("error running Graph. Graph output does not contain expected strings. Returned: %s", graphOutput)
}
})
}
26 changes: 26 additions & 0 deletions tfexec/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ func Destroy(destroy bool) *DestroyFlagOption {
return &DestroyFlagOption{destroy}
}

type DrawCyclesOption struct {
drawCycles bool
}

// DrawCycles represents the -draw-cycles flag.
func DrawCycles(drawCycles bool) *DrawCyclesOption {
return &DrawCyclesOption{drawCycles}
}

type DryRunOption struct {
dryRun bool
}
Expand Down Expand Up @@ -222,6 +231,15 @@ func Parallelism(n int) *ParallelismOption {
return &ParallelismOption{n}
}

type GraphPlanOption struct {
file string
}

// GraphPlan represents the -plan flag which is a specified plan file string
func GraphPlan(file string) *GraphPlanOption {
return &GraphPlanOption{file}
}

type PlatformOption struct {
platform string
}
Expand Down Expand Up @@ -344,6 +362,14 @@ func Target(resource string) *TargetOption {
return &TargetOption{resource}
}

type GraphTypeOption struct {
graphType string
}

func GraphType(graphType string) *GraphTypeOption {
return &GraphTypeOption{graphType}
}

type UpdateOption struct {
update bool
}
Expand Down
2 changes: 2 additions & 0 deletions tfexec/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import (

var (
tf0_4_1 = version.Must(version.NewVersion("0.4.1"))
tf0_5_0 = version.Must(version.NewVersion("0.5.0"))
tf0_6_13 = version.Must(version.NewVersion("0.6.13"))
tf0_7_7 = version.Must(version.NewVersion("0.7.7"))
tf0_8_0 = version.Must(version.NewVersion("0.8.0"))
tf0_10_0 = version.Must(version.NewVersion("0.10.0"))
tf0_12_0 = version.Must(version.NewVersion("0.12.0"))
tf0_13_0 = version.Must(version.NewVersion("0.13.0"))
Expand Down

0 comments on commit 38e1272

Please sign in to comment.