Skip to content

Commit

Permalink
Add ability to generate png with inframap (#38)
Browse files Browse the repository at this point in the history
The APIs for map generation via inframap are updated to support
generating png representations of the map using GraphViz's `dot` command.
For this purpose, graphviz is installed in the Docker image, and an
option struct is added to `api.InfraMap()`.

The command line interface is slightly refactored to allow for
tool-specific options in order to support this new feature.

The ability to generate png is available both via the command line and
the REST API. The /map endpoint will accept a "png" boolean attribute in
the JSON body.

Co-authored-by: Ido Perlmuter <ido.perlmuter@gofirefly.io>
Approved-by: Sefi Genis <sefi@gofirefly.io>
  • Loading branch information
ido50 and Ido Perlmuter authored Apr 9, 2022
1 parent 3b7e548 commit 6625ac9
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 118 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RUN make -e build && make -e test
RUN chmod +x ./bin/validiac

FROM alpine:3.14
RUN apk add -u ca-certificates git
RUN apk add -u ca-certificates git graphviz
COPY --from=0 /validiac/bin/* /validiac/bin/
ENV HOME="/validiac/bin/"
ENV BIN_PATH="/validiac/bin/"
Expand Down
28 changes: 17 additions & 11 deletions backend/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ package api
import (
"fmt"
"io/ioutil"
"os"
"os"
)

// Tool represents the name of a third-party tool support by validiac
type Tool string

const (
ToolTFLint Tool = "tflint"
ToolTFSec Tool = "tfsec"
// ToolTFLint represents the tflint tool
ToolTFLint Tool = "tflint"

// ToolTFSec represents the tfsec tool
ToolTFSec Tool = "tfsec"

// ToolInfracost represents the infracost tool
ToolInfracost Tool = "infracost"
ToolInframap Tool = "inframap"
)

type ToolFunc func(in []byte) (out []byte, err error)
// ToolInframap represents the inframap tool
ToolInframap Tool = "inframap"
)

func asTempFile(dir, ext string, in []byte) (path string, err error) {
tmpfile, err := ioutil.TempFile(dir, fmt.Sprintf("validiac-*%s", ext))
Expand Down Expand Up @@ -48,9 +54,9 @@ func asTempDir(ext string, in []byte) (path string, err error) {
}

func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if len(value) == 0 {
return defaultValue
}
return value
value := os.Getenv(key)
if len(value) == 0 {
return defaultValue
}
return value
}
27 changes: 20 additions & 7 deletions backend/api/infracost.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
package api

import (
"fmt"
"github.com/thoas/go-funk"
"os"
"os/exec"
"fmt"
"os"
"os/exec"

"github.com/thoas/go-funk"
)

var InfraCostExec = getEnv("INFRACOST_EXEC", fmt.Sprintf("%s/infracost", BIN_PATH))

func InfraCost(in []byte) ([]byte, error) {
var infraCostApiKey = funk.GetOrElse(os.Getenv("INFRACOST_API_KEY"), "infracost-api-key")
var infraCostApiKey = funk.GetOrElse(os.Getenv("INFRACOST_API_KEY"), "infracost-api-key")
path, err := asTempDir(".tf", in)
if err != nil {
return nil, err
}

defer os.Remove(path) // nolint: errcheck

cmd := exec.Command(InfraCostExec, "breakdown", "--path", path, "--terraform-parse-hcl", "--no-color", "--log-level=error")
cmd.Env = append(cmd.Env, fmt.Sprintf("INFRACOST_API_KEY=%s", infraCostApiKey), fmt.Sprintf("PATH=%s", os.Getenv("PATH")))
cmd := exec.Command(
InfraCostExec,
"breakdown",
"--path",
path,
"--terraform-parse-hcl",
"--no-color",
"--log-level=error",
)
cmd.Env = append(
cmd.Env,
fmt.Sprintf("INFRACOST_API_KEY=%s", infraCostApiKey),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
)
return cmd.CombinedOutput()
}
78 changes: 72 additions & 6 deletions backend/api/inframap.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,86 @@
package api

import (
"fmt"
"os"
"os/exec"
"fmt"
"io"
"os"
"os/exec"
)

var InfraMapExec = getEnv("INFRAMAP_EXEC", fmt.Sprintf("%s/inframap", BIN_PATH))
var InfraMapExec = getEnv("INFRAMAP_EXEC", fmt.Sprintf("%s/inframap", BIN_PATH))

func InfraMap(in []byte) ([]byte, error) {
type InfraMapOpts struct {
Png bool
}

func InfraMap(in []byte, opts InfraMapOpts) ([]byte, error) {
path, err := asTempFile("", "", in)
if err != nil {
return nil, err
}

defer os.Remove(path) // nolint: errcheck

return exec.Command(InfraMapExec, "generate", "--raw", "--show-icons=true", "--connections=false", "--clean=false", path).CombinedOutput()
args := []string{
"generate",
"--show-icons=true",
"--connections=false",
"--clean=false",
}

if !opts.Png {
args = append(args, "--raw")
}

cmd := exec.Command(InfraMapExec, append(args, path)...)

if opts.Png {
// get a pipe of stdout, we're going to pipe output to dot (from graphviz)
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("failed getting stdout pipe of inframap: %w", err)
}

if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("failed starting inframap: %w", err)
}

dot := exec.Command("dot", "-Tpng")
stdin, err := dot.StdinPipe()
if err != nil {
return nil, fmt.Errorf("failed getting stdin pipe for dot: %w", err)
}

var pipeErr error

go func() {
defer stdin.Close()

// copy from inframap's stdout to dot's stdin
r := io.TeeReader(stdout, stdin)
if _, err := io.ReadAll(r); err != nil {
pipeErr = fmt.Errorf("failed reading from inframap: %w", err)
return
}

// wait for inframap to exit
if err = cmd.Wait(); err != nil {
pipeErr = fmt.Errorf("inframap failed: %w", err)
return
}
}()

out, err := dot.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("dot failed: %w", err)
}

if pipeErr != nil {
return nil, pipeErr
}

return out, nil
}

return cmd.CombinedOutput()
}
62 changes: 31 additions & 31 deletions backend/api/tflint.go
Original file line number Diff line number Diff line change
@@ -1,58 +1,58 @@
package api

import (
"fmt"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
"github.com/hashicorp/hcl2/hclparse"
"github.com/thoas/go-funk"
"os"
"os/exec"
"strings"
"fmt"
"os"
"os/exec"
"strings"

"github.com/hashicorp/hcl2/hcl/hclsyntax"
"github.com/hashicorp/hcl2/hclparse"
"github.com/thoas/go-funk"
)

var TFLintExec = getEnv("TFLINT_EXEC", fmt.Sprintf("%s/tflint", BIN_PATH))
var TFLintConfig = getEnv("TFLINT_config", fmt.Sprintf("%s/.tflint.hcl", BIN_PATH))

func TFLint(in []byte) ([]byte, error) {
path, err := asTempDir(".tf", in)
if err != nil {
return nil, err
}
path, err := asTempDir(".tf", in)
if err != nil {
return nil, err
}

defer os.RemoveAll(path)
defer os.RemoveAll(path)

err, hasAWS, hasAzure, hasGoogle := extractActiveProviders(in)
if err != nil{
return nil, err
}

var args = []string{}
//Adding config args if one of the providers is enabled
if hasAWS||hasAzure||hasGoogle{
args = append(args, fmt.Sprintf("--config=%s", TFLintConfig))
}
var args = []string{}
//Adding config args if one of the providers is enabled
if hasAWS || hasAzure || hasGoogle {
args = append(args, fmt.Sprintf("--config=%s", TFLintConfig))
}

if hasAWS{
args = append(args, "--enable-plugin=aws")
}
if hasAWS {
args = append(args, "--enable-plugin=aws")
}

if hasAzure{
args = append(args, "--enable-plugin=azurerm")
}
if hasAzure {
args = append(args, "--enable-plugin=azurerm")
}

if hasGoogle{
args = append(args, "--enable-plugin=google")
}
if hasGoogle {
args = append(args, "--enable-plugin=google")
}

args = append(args, path)
args = append(args, "--no-color")
args = append(args, "--force")
args = append(args, path)
args = append(args, "--no-color")
args = append(args, "--force")
return exec.Command(TFLintExec, args...).CombinedOutput()
}

func tflintInit() ([]byte, error) {
var cmd = exec.Command(TFLintExec, "--init", "-c", TFLintConfig)
return cmd.CombinedOutput()
return exec.Command(TFLintExec, "--init", "-c", TFLintConfig).CombinedOutput()
}

func extractActiveProviders(hcl []byte) (error, bool, bool, bool) {
Expand Down
4 changes: 2 additions & 2 deletions backend/api/tfsec.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package api

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

Expand Down
Loading

0 comments on commit 6625ac9

Please sign in to comment.