Skip to content

Commit

Permalink
blast-radius extended inital commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nbharti1 committed Oct 8, 2021
1 parent a7ec4ef commit 3a6fdb9
Show file tree
Hide file tree
Showing 39 changed files with 1,640 additions and 509 deletions.
120 changes: 75 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,82 @@
# Blast Radius

[![CircleCI](https://circleci.com/gh/28mm/blast-radius/tree/master.svg?style=svg)](https://circleci.com/gh/28mm/blast-radius/tree/master)
[![PyPI version](https://badge.fury.io/py/BlastRadius.svg)](https://badge.fury.io/py/BlastRadius)

[terraform]: https://www.terraform.io/
[examples]: https://28mm.github.io/blast-radius-docs/
[overlayfs]:

_Blast Radius_ is a tool for reasoning about [Terraform][] dependency graphs
with interactive visualizations.
_Blast Radius_ is a tool for reasoning about [Terraform][] dependency graphs with interactive visualizations.

Use _Blast Radius_ to:

* __Learn__ about *Terraform* or one of its providers through real [examples][]
* __Document__ your infrastructure
* __Reason__ about relationships between resources and evaluate changes to them
* __Interact__ with the diagram below (and many others) [in the docs][examples]
* __Interact__ with the diagrams below (and many others) [in the docs][examples]

![screenshot](doc/blastradius-interactive.png)
---

## Blast Radius
![screenshot](doc/blastradiusext.png)

---

## Prerequisites

* [Graphviz](https://www.graphviz.org/)
* [Python](https://www.python.org/) 3.7 or newer
* [Terraform][] 0.12.x or newer

> __Note:__ For macOS you can `brew install graphviz`
---

## Quickstart

The fastest way to get up and running with *Blast Radius* is to install it with
`pip` to your pre-existing environment:
For fastest way to get up and running with blast-radius is as follows:

```sh
pip install blastradius
```
* Download and install the wheel files from the [release](https://github.com/nishubharti/blast-radius/releases)

Once installed just point *Blast Radius* at any initialized *Terraform*
directory:
```
copy the blastradius/server/static/images folder to the terraform directory
```
install the wheel file
```
easy_install blastradius-0.1.25.1-py3-none-any.whl
```
or
```
pip3 blastradius-0.1.25.1-py3-none-any.whl
```

```sh
blast-radius --serve /path/to/terraform/directory
```
* Enrich the Blast Radius diagrams with the outcome of Terraform plan actions:
```
terraform plan --out tfplan.binary
terraform show -json tfplan.binary > tfplan.json
```
for including cost and policy information into blast-radius cost.json and policy.json file need to be stored into the working directory.

* Once installed just point Blast Radius at any initialized Terraform directory:
```sh
blast-radius --serve /path/to/terraform/directory
```

* Go to the browser link http://127.0.0.1:5000/ to view the Blast Radius diagram for the terraform file.

![BlastRadius](doc/blastradiusext.png)

The enrichments include - information from the Plan file, State file , cost file and time file .
Click the columns adjacent to the Resource Names to view these enrichment in the side panel view.

![BlastRadiusExt](doc/blast-radius-ext.png)

And you will shortly be rewarded with a browser link http://127.0.0.1:5000/.
---

## Build your own wheel file

* Create wheel file of this repo
```sh
python3 setup.py sdist bdist_wheel
```
---

## Docker

Expand All @@ -50,34 +85,28 @@ And you will shortly be rewarded with a browser link http://127.0.0.1:5000/.

To launch *Blast Radius* for a local directory by manually running:

```sh
docker run --rm -it -p 5000:5000 \
-v $(pwd):/data:ro \
--security-opt apparmor:unconfined \
--cap-add=SYS_ADMIN \
28mm/blast-radius
```
* create a dockerhub account
```sh
docker build -t <dockerhub_username>/blast-radius:v1 .
docker push <dockerhub_username>/blast-radius:v1
```

A slightly more customized variant of this is also available as an example
[docker-compose.yml](./examples/docker-compose.yml) usecase for Workspaces.
```sh
docker run --cap-add=SYS_ADMIN -dit -p 5000:5000 -v <path>:/data:ro <dockerhub_username>/blast-radius:v1
```

### Docker configurations

*Terraform* module links are saved as _absolute_ paths in relative to the
project root (note `.terraform/modules/<uuid>`). Given these paths will vary
betwen Docker and the host, we mount the volume as read-only, assuring we don't
ever interfere with your real environment.
betwen Docker and the host, we mount the volume as read-only, assuring we don't ever interfere with your real environment.

However, in order for *Blast Radius* to actually work with *Terraform*, it needs
to be initialized. To accomplish this, the container creates an [overlayfs][]
that exists within the container, overlaying your own, so that it can operate
However, in order for *Blast Radius* to actually work with *Terraform*, it needs to be initialized as well as planned compulsory. To accomplish this, the container creates an [overlayfs][] that exists within the container, overlaying your own, so that it can operate
independently. To do this, certain runtime privileges are required --
specifically `--cap-add=SYS_ADMIN`.

For more information on how this works and what it means for your host, check
out the [runtime privileges][privileges] documentation.

#### Docker & Subdirectories
### Docker & Subdirectories

If you organized your *Terraform* project using stacks and modules,
*Blast Radius* must be called from the project root and reference them as
Expand All @@ -101,14 +130,12 @@ It consists of 3 modules `foo`, `bar` and `dead`, followed by one `beef` stack.
To apply *Blast Radius* to the `beef` stack, you would want to run the container
with the following:

```sh
$ cd project
$ docker run --rm -it -p 5000:5000 \
-v $(pwd):/data:ro \
--security-opt apparmor:unconfined \
--cap-add=SYS_ADMIN \
28mm/blast-radius --serve stacks/beef
```
```sh
$ cd project
$ docker run --cap-add=SYS_ADMIN -dit -p 5000:5000 -v <pathofdirectory>:/data:ro <dockerhub_username>/blast-radius:v1
```

---

## Embedded Figures

Expand All @@ -121,11 +148,14 @@ You will need the following:

You can read more details in the [documentation](doc/embedded.md)

---

## Implementation Details

*Blast Radius* uses the [Graphviz][] package to layout graph diagrams,
[PyHCL](https://github.com/virtuald/pyhcl) to parse [Terraform][] configuration,
and [d3.js](https://d3js.org/) to implement interactive features and animations.
[hcl] to parse [Terraform][] configuration, and [d3.js](https://d3js.org/) to implement interactive features and animations.

---

## Further Reading

Expand Down
21 changes: 14 additions & 7 deletions bin/blast-radius
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ from blastradius.handlers.apply import Apply
from blastradius.handlers.terraform import Terraform
from blastradius.server.server import app

from blastradius.server.server import simple_graph

def main():

parser = parser = argparse.ArgumentParser(description='blast-radius: Interactive Terraform Graph Visualizations')
Expand All @@ -28,7 +30,8 @@ def main():
output_group.add_argument('--dot', action='store_const', const=True, default=False, help='print the graphviz/dot representation of the Terraform graph')
output_group.add_argument('--svg', action='store_const', const=True, default=False, help='print the svg representation of the Terraform graph')
output_group.add_argument('--serve', action='store_const', const=True, default=False, help='spins up a webserver with an interactive Terraform graph')

output_group.add_argument('--svg-ext', action='store_const', const=True, default=False, help='download the simple svg of modified svg representation of the Terraform graph')

parser.add_argument('--graph', type=str, help='`terraform graph` output (defaults to stdin)', default=sys.stdin)

# options to limit, re-focus, and re-center presentation of larger graphs.
Expand All @@ -49,11 +52,11 @@ def main():
app.run(host='0.0.0.0',port=args.port)
sys.exit(0)

elif args.json or args.dot or args.svg:
elif args.json or args.dot or args.svg or args.svg_ext:
if args.graph is sys.stdin:
dot = DotGraph('', file_contents=sys.stdin.read())
else:
dot = DotGraph(args.graph)
dot = DotGraph('',file_contents=simple_graph())

# we might not want to show every node in the depedency graph
# specifying --module-depth is an easy way to limit detail
Expand All @@ -76,18 +79,22 @@ def main():
parser.print_help()
sys.exit(1)
dot.focus(f_node)

if args.json:
tf = Terraform(args.directory)
for node in dot.nodes:
node.definition = tf.get_def(node)

if args.json:
print(dot.json())
f = open("visualization.json", "a")
f.write(dot.json())
f.close()
elif args.dot:
print(dot.dot())
elif args.svg:
print(dot.svg())
elif args.svg_ext:
f = open("visualization.svg", "a")
f.write(dot.svg())
f.close()
else:
parser.print_help()

Expand Down
88 changes: 34 additions & 54 deletions blastradius/handlers/apply.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,35 @@
# standard libraries
import re
from __future__ import print_function
import json

# 1st party libraries
from blastradius.graph import Graph, Node, Edge
from blastradius.handlers.dot import DotNode
from blastradius.util import Re

class Apply(Graph):
def __init__(self, filename):
self.filename = filename
self.contents = ''
self.nodes = [] # we can populate this,
self.edges = [] # but not this!

ansi_escape = re.compile(r'\x1b[^m]*m')
with open(filename, 'r') as f:
self.contents = ansi_escape.sub('', f.read())

# example output:
#
# aws_vpc.default: Creation complete after 4s (ID: vpc-024f7a64)
# ...
# aws_key_pair.auth: Creating...
# fingerprint: "" => "<computed>"
# key_name: "" => "default-key"
# ...
# aws_instance.web: Still creating... (10s elapsed)
# aws_instance.web: Still creating... (20s elapsed)
# aws_instance.web (remote-exec): Connecting to remote host via SSH...
# aws_instance.web (remote-exec): Host: 1.2.3.4
# aws_instance.web (remote-exec): User: ubuntu
# ...

node_begin_re =r'(?P<name>\S+)\:\s+Creating...'
node_compl_re = r'(?P<name>\S+)\:\s+Creation\s+complete\s+after\s+(?P<duration>\S+)\s+'
node_still_re = r'(?P<name>\S+)\:\s+Still\s+creating\.\.\.\s+\((?P<duration>\S+)\s+'

for line in self.contents.splitlines():

r = Re()
if r.match(node_begin_re, line):




break




print(self.contents)


import sys
import jinja2
import json
import subprocess
import os.path
from os import path

class Apply():
def __init__(self,filename=None):
self.apply_resource_info = []
#reading from state file
if filename == None:
if(path.exists("terraform.tfstate")):
with open("terraform.tfstate", 'r') as f:
data = json.load(f)
else:
data = ""
else:
with open(filename, 'r') as f:
data = json.load(f)

if (data) :
for _, var in enumerate(data["resources"]):
temp_data = dict()
temp_data = var
self.apply_resource_info.append(temp_data)

else:
self.apply_resource_info.append("not applied")

def json(self):
my_json_string = json.dumps(self.apply_resource_info,indent=4, sort_keys=True)
return my_json_string
30 changes: 30 additions & 0 deletions blastradius/handlers/controls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import json
import os.path
from os import path

class Controls():
def __init__(self,filename=None):
self.resource_controls_info = []
if filename == None:
if(path.exists("policy.json")):
with open("policy.json", 'r') as f:
data = json.load(f)
else:
data = ""
else:
with open(filename, 'r') as f:
data = json.load(f)

if (data) :
for _, var in enumerate(data["resources"]):
temp_data = dict()
temp_data = var
self.resource_controls_info.append(temp_data)

else:
self.resource_controls_info.append("not available")


def json(self):
controls_json = json.dumps(self.resource_controls_info,indent=4, sort_keys=True)
return controls_json
Loading

0 comments on commit 3a6fdb9

Please sign in to comment.