Skip to content

Commit

Permalink
cmd: Added new flag --description-file
Browse files Browse the repository at this point in the history
Which will return a description file of the Nodes, which will contain
all the information from the read configuration.

We had to change the generate.FromHCL function to return now a new
parameter, so now it's the same as the generate.FromState.

Also the returned cofig from both of those methods has changed formats
as it's now just the resources on the first level without being them
nested inside a 'resources' and then grouped by resource type. It was
a newsense so we changed that
  • Loading branch information
xescugc committed Apr 28, 2022
1 parent b43f418 commit ed067f0
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 71 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## [Unreleased]

### Added

- New flag `--description-file` to generate a description file of the Graph Nodes
([Issue #178](https://github.com/cycloidio/inframap/issues/178))

### Changed

- Updated the `tfdocs` version
Expand Down
49 changes: 38 additions & 11 deletions cmd/generate.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"encoding/json"
"fmt"
"os"
"strings"
Expand All @@ -14,12 +15,13 @@ import (
)

var (
printerType string
raw bool
clean bool
connections bool
showIcons bool
externalNodes bool
printerType string
raw bool
clean bool
connections bool
showIcons bool
externalNodes bool
descriptionFile string

generateCmd = &cobra.Command{
Use: "generate [FILE]",
Expand All @@ -37,15 +39,16 @@ var (
}

var (
g *graph.Graph
err error
g *graph.Graph
gdesc map[string]interface{}
err error
)

if tfstate {
g, _, err = generate.FromState(file, opt)
g, gdesc, err = generate.FromState(file, opt)
} else {
if len(file) == 0 {
g, err = generate.FromHCL(afero.NewOsFs(), path, opt)
g, gdesc, err = generate.FromHCL(afero.NewOsFs(), path, opt)
} else {
fs := afero.NewMemMapFs()
path = "module.tf"
Expand All @@ -66,7 +69,7 @@ var (
return err
}

g, err = generate.FromHCL(fs, path, opt)
g, gdesc, err = generate.FromHCL(fs, path, opt)
}
}

Expand All @@ -79,6 +82,29 @@ var (
return err
}

if descriptionFile != "" && gdesc != nil {
df, err := os.OpenFile(descriptionFile, os.O_APPEND|os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return err
}
defer df.Close()

// The gdesc has the description of all the elements of the graph, including the
// edges so we have to remove them from the output
for can := range gdesc {
if _, err := g.GetNodeByCanonical(can); err != nil {
delete(gdesc, can)
}
}

b, err := json.Marshal(gdesc)
if err != nil {
return err
}

df.Write(b)
}

popt := printer.Options{
ShowIcons: showIcons,
}
Expand All @@ -99,4 +125,5 @@ func init() {
generateCmd.Flags().BoolVar(&connections, "connections", true, "Connections will apply the logic of the provider to remove resources that are not nodes")
generateCmd.Flags().BoolVar(&showIcons, "show-icons", true, "Toggle the icons on the printed graph")
generateCmd.Flags().BoolVar(&externalNodes, "external-nodes", true, "Toggle the addition of external nodes like 'im_out' (used to show ingress connections)")
generateCmd.Flags().StringVar(&descriptionFile, "description-file", "", "On the given file (will be created or overwritten) we'll output the description of the returned graph, with the attributes of all the visible nodes")
}
41 changes: 23 additions & 18 deletions generate/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (

// FromHCL generates a new graph from the HCL on the path,
// it can be a file or a Module/Dir
func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, map[string]interface{}, error) {
parser := configs.NewParser(fs)

g := graph.New()
Expand All @@ -35,18 +35,18 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
} else {
f, dgs := parser.LoadConfigFile(path)
if dgs.HasErrors() {
return nil, errors.New(dgs.Error())
return nil, nil, errors.New(dgs.Error())
}
mod, diags = configs.NewModule([]*configs.File{f}, nil)
}

if diags.HasErrors() {
return nil, errors.New(diags.Error())
return nil, nil, errors.New(diags.Error())
}

// nodeCanID holds as key the `aws_alb.front` (graph.Node.Canonical)
// nodeCanIDs holds as key the `aws_alb.front` (graph.Node.Canonical)
// and as value the UUID (graph.Node.ID) we give to it
nodeCanID := make(map[string]string)
nodeCanIDs := make(map[string][]string)

// nodeIDEdges holds as key the UUID (graph.Node.ID) and as value
// all the edges it has, in this case it's the `depends_on` values
Expand All @@ -60,7 +60,7 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
if !opt.Raw {
opt, err = checkHCLProviders(mod, opt)
if err != nil {
return nil, err
return nil, nil, err
}
}

Expand All @@ -70,7 +70,7 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
if errors.Is(err, errcode.ErrProviderNotFound) {
continue
}
return nil, err
return nil, nil, err
}

// If it's not a Node or Edge we ignore it
Expand All @@ -80,7 +80,7 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {

res, err := pv.Resource(rs)
if err != nil {
return nil, err
return nil, nil, err
}
n := &graph.Node{
ID: uuid.NewV4().String(),
Expand All @@ -90,10 +90,10 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {

err = g.AddNode(n)
if err != nil {
return nil, err
return nil, nil, err
}

nodeCanID[n.Canonical] = n.ID
nodeCanIDs[n.Canonical] = append(nodeCanIDs[n.Canonical], n.ID)

links := make(map[string][]string)
body, ok := rv.Config.(*hclsyntax.Body)
Expand All @@ -109,7 +109,7 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
// and manually deal with them. For what I've tested
// the Blocks (so egess an ingress for example) do not
// work and fail so we should find a workaround.
return nil, errcode.ErrGenerateFromJSON
return nil, nil, errcode.ErrGenerateFromJSON
}

for _, resources := range links {
Expand All @@ -130,10 +130,11 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
rk = strings.Join(keys[:len(keys)-1], ".")
}

tnid, ok := nodeCanID[rk]
tnids, ok := nodeCanIDs[rk]
if !ok {
continue
}
tnid := tnids[0]

err := g.AddEdge(&graph.Edge{
ID: uuid.NewV4().String(),
Expand All @@ -145,7 +146,7 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
if errors.Is(err, errcode.ErrGraphAlreadyExistsEdge) {
continue
}
return nil, err
return nil, nil, err
}

}
Expand All @@ -154,7 +155,7 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {
// call the preprocess method for each
// TF provider in the file
if err := preprocess(g, resourcesRawConfig, opt); err != nil {
return nil, err
return nil, nil, err
}

if opt.Clean {
Expand All @@ -163,24 +164,28 @@ func FromHCL(fs afero.Fs, path string, opt Options) (*graph.Graph, error) {

err = fixEdges(g, resourcesRawConfig, opt)
if err != nil {
return nil, err
return nil, nil, err
}

if opt.Connections {
err = mutate(g, opt)
if err != nil {
return nil, err
return nil, nil, err
}
}

if opt.Clean {
err = cleanHangingEdges(g, opt)
if err != nil {
return nil, err
return nil, nil, err
}
}

return g, nil
endCfg, err := buildConfig(g, resourcesRawConfig, nodeCanIDs)
if err != nil {
return nil, nil, err
}
return g, endCfg, nil
}

// getBodyLinks gets all the variables used and the key in which
Expand Down
16 changes: 8 additions & 8 deletions generate/hcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func TestFromHCL_AWS(t *testing.T) {
t.Run("SuccessSG", func(t *testing.T) {
fs := afero.NewOsFs()

g, err := generate.FromHCL(fs, "./testdata/aws_hcl_sg.tf", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
g, cfg, err := generate.FromHCL(fs, "./testdata/aws_hcl_sg.tf", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
require.NoError(t, err)
require.NotNil(t, g)

Expand Down Expand Up @@ -67,15 +67,15 @@ func TestFromHCL_AWS(t *testing.T) {
},
}

assertEqualGraph(t, eg, g, nil)
assertEqualGraph(t, eg, g, cfg)
})
}

func TestFromHCL_FlexibleEngine(t *testing.T) {
t.Run("SuccessSG", func(t *testing.T) {
fs := afero.NewOsFs()

g, err := generate.FromHCL(fs, "./testdata/flexibleengine_hcl.tf", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
g, cfg, err := generate.FromHCL(fs, "./testdata/flexibleengine_hcl.tf", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
require.NoError(t, err)
require.NotNil(t, g)

Expand Down Expand Up @@ -103,15 +103,15 @@ func TestFromHCL_FlexibleEngine(t *testing.T) {
},
}

assertEqualGraph(t, eg, g, nil)
assertEqualGraph(t, eg, g, cfg)
})
}

func TestFromHCL_Google(t *testing.T) {
t.Run("Success", func(t *testing.T) {
fs := afero.NewOsFs()

g, err := generate.FromHCL(fs, "./testdata/google_hcl.tf", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
g, cfg, err := generate.FromHCL(fs, "./testdata/google_hcl.tf", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
require.NoError(t, err)
require.NotNil(t, g)

Expand All @@ -135,15 +135,15 @@ func TestFromHCL_Google(t *testing.T) {
},
}

assertEqualGraph(t, eg, g, nil)
assertEqualGraph(t, eg, g, cfg)
})
}

func TestFromHCL_Module(t *testing.T) {
t.Run("SuccessSG", func(t *testing.T) {
fs := afero.NewOsFs()

g, err := generate.FromHCL(fs, "./testdata/tf-module/", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
g, cfg, err := generate.FromHCL(fs, "./testdata/tf-module/", generate.Options{Clean: true, Connections: true, ExternalNodes: true})
require.NoError(t, err)
require.NotNil(t, g)

Expand Down Expand Up @@ -197,6 +197,6 @@ func TestFromHCL_Module(t *testing.T) {
},
}

assertEqualGraph(t, eg, g, nil)
assertEqualGraph(t, eg, g, cfg)
})
}
6 changes: 2 additions & 4 deletions generate/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,8 @@ func assertEqualGraph(t *testing.T, expected, actual *graph.Graph, actualCfg map
// it'll be ignored if nil
if actualCfg != nil {
actualCans := make([]string, 0, 0)
for k, v := range actualCfg["resource"].(map[string]interface{}) {
for n := range v.(map[string]interface{}) {
actualCans = append(actualCans, fmt.Sprintf("%s.%s", k, n))
}
for k, _ := range actualCfg {
actualCans = append(actualCans, k)
}

expectedCans := make([]string, 0, 0)
Expand Down
40 changes: 10 additions & 30 deletions generate/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,23 +365,15 @@ func buildConfig(g *graph.Graph, cfg map[string]map[string]interface{}, nodeCanI
return nil, fmt.Errorf("could not find config of node %q: %w", n.Canonical, errcode.ErrInvalidTFStateFile)
}

// Canonical = module.name.aws_security_group.front-port80
// rt == path[-2] == resource Type ex: module.name.aws_security_group
// rn == path[-1] == resource Name ex: front-port80
path := strings.Split(n.Canonical, ".")
rn := path[len(path)-1]
rt := strings.Join(path[:len(path)-1], ".")

if _, ok := endCfg[rt]; !ok {
endCfg[rt] = make(map[string]interface{})
}

if _, ok := endCfg[rt].(map[string]interface{})[rn]; ok {
if _, ok := endCfg[n.Canonical]; ok {
// If we have it already set, then it's not a valid config
return nil, fmt.Errorf("repeated config node for %q: %w", n.Canonical, errcode.ErrInvalidTFStateFile)
}

endCfg[rt].(map[string]interface{})[rn] = c
// Delete the key added by the HCL generator
delete(c, provider.HCLCanonicalKey)

endCfg[n.Canonical] = c
}

for _, e := range g.Edges {
Expand All @@ -404,29 +396,17 @@ func buildConfig(g *graph.Graph, cfg map[string]map[string]interface{}, nodeCanI
return nil, fmt.Errorf("could not find config of the Node %q: %w", can, errcode.ErrInvalidTFStateFile)
}

// Canonical = module.name.aws_security_group.front-port80
// rt == path[-2] == resource Type ex: module.name.aws_security_group
// rn == path[-1] == resource Name ex: front-port80
path := strings.Split(can, ".")
rn := path[len(path)-1]
rt := strings.Join(path[:len(path)-1], ".")

if _, ok := endCfg[rt]; !ok {
endCfg[rt] = make(map[string]interface{})
}

if _, ok := endCfg[rt].(map[string]interface{})[rn]; ok {
if _, ok := endCfg[can]; ok {
// As a connection canonical can be shared between different
// connections this will happen so we ignore it
continue
}

endCfg[rt].(map[string]interface{})[rn] = c
}
}
// Delete the key added by the HCL generator
delete(c, provider.HCLCanonicalKey)

endCfg = map[string]interface{}{
"resource": endCfg,
endCfg[can] = c
}
}

return endCfg, nil
Expand Down

0 comments on commit ed067f0

Please sign in to comment.