aws-edges 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,163 @@
1
+ # aws-edges
2
+
3
+ ## Overview
4
+
5
+ **aws-edges** allows you to created high level graphviz digraphs of your AWS components. Currently this does not cover everything returned from the AWS Ruby SDK, only select components so far.
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ $ gem install aws-edges
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```
16
+ $ ./aws-edges -h
17
+ Usage: aws-edges [options]
18
+ -c, --config [CONFIG_FILE] Config file
19
+ -f, --config-format [yaml|json] Config file format (yaml or json)
20
+ -a [ACCESS_KEY_ID], AWS Access Key ID
21
+ --access-key-id
22
+ -s [SECRET_ACCESS_KEY], AWS Secret Access Key
23
+ --secret-key-id
24
+ -r, --region [REGION] AWS Region (ie: us-east-1)
25
+ -C, --list-colors Prints out a list of supported colors
26
+ -S, --list-shapes Prints out a list of supported shapes
27
+ -?, --help Shows this message
28
+ ```
29
+
30
+ ## Configuration Example
31
+
32
+ There are some examples located in the 'examples' directory. Below is one of them. The *sources* section tells the *aws-edges* script what to pull from AWS. And the rest is pretty self explanatory.
33
+
34
+ The basic structure is show below. The `name` key provides the output name of the graph generated. Which will generate both a *.dot* and *.png* file by default.
35
+
36
+ ```
37
+ ---
38
+ name: "my graph name"
39
+ rotate: true
40
+ sources:
41
+ - "ec2"
42
+ - "subnet"
43
+ edges:
44
+ -
45
+ from: "ec2_hypervisor"
46
+ from_color: "orange"
47
+ from_shape: "invhouse"
48
+ to: "ec2_virtualization_type"
49
+ to_color: "coral"
50
+ to_shape: "diamond"
51
+ -
52
+ from: "ec2_virtualization_type"
53
+ to: "ec2_instance_id"
54
+ -
55
+ from: "ec2_instance_id"
56
+ from_color: "dodgerblue"
57
+ to: "ec2_subnet_id"
58
+ to_color: "darkturquoise"
59
+ -
60
+ from: "subnet_cidr_block"
61
+ to: "subnet_availability_zone"
62
+ to_color: "crimson"
63
+ cluster:
64
+ label: "Subnet Legend"
65
+ edges:
66
+ -
67
+ from: "subnet_subnet_id"
68
+ to: "subnet_cidr_block"
69
+ to_color: "firebrick"
70
+ ```
71
+ ### Specifying output format
72
+
73
+ If you like to save the graph in a different format other than the default `png`. You can specify the `save_as: "pdf"` or some other image format such as tiff.
74
+
75
+ ### Mapping a 'many' node
76
+
77
+ What is a 'many' node? Simply put, it is a object containing many entries or an Hash of an Array of Hashes.
78
+
79
+ The syntax for mapping a 'many' node is `"redshift_cluster_nodes-private_ip_address"`. The `-` character indicating that `redshift_cluster_nodes` is an Array and you want all the `private_ip_address` objects/property.
80
+
81
+ ### Grouping edges
82
+
83
+ Sometime you would like to visually group a set or sets of edges together so they appear in a container. This can help distinguish the relation between edge objects. You will need to group your objects using the `cluster` key and can label it using something like; `label: "My VPCs"`. Below is an example config snippet:
84
+
85
+ ```
86
+ ---
87
+ name: "example"
88
+ rotate: true
89
+ sources:
90
+ - "vpc"
91
+ cluster:
92
+ label: "Virtual Private Clouds"
93
+ edges:
94
+ -
95
+ to: "vpc_cidr_block"
96
+ from: "vpc_vpc_id"
97
+ ```
98
+
99
+ ### Changing the layout
100
+
101
+ By default graphs are generated in a horizontal top down structure. If you like a vertical left to right representation you will need to set the `rotate: true` property in your config like so:
102
+
103
+ ```
104
+ $ cat myconfig.yml
105
+ ---
106
+ name: "example"
107
+ rotate: true
108
+ sources:
109
+ - "vpc"
110
+ - "ec2"
111
+ - "subnet"
112
+ ```
113
+
114
+ ### Adding edge colors
115
+
116
+ In order to make it easier to identify groups of like edges, coloring (fill) support has been added. Being that this utilizes the `graph` gem it supports all of the colors supported by it.
117
+
118
+ [Visit Graphviz Colors to see what they look like](http://www.graphviz.org/content/color-names)
119
+
120
+ To see what colors are available run the following command:
121
+
122
+ ```
123
+ $ aws-edges -C
124
+ ```
125
+
126
+ or
127
+
128
+ ```
129
+ $ aws-edges --list-colors
130
+ ```
131
+
132
+ To use colors, in your config simply add either `to_color: "orange"` or `from_color: "brown"` to the `edges` section of the config. (See example above)
133
+
134
+ ### Adding edges shapes
135
+
136
+ In order to make it easier to identify *like* edges, shape support has been added. As with colors, it supports the shapes available to the `graph` gem on which the gem requires.
137
+
138
+ [Visit Graphviz Shapes to see what the look like](http://www.graphviz.org/content/node-shapes)
139
+
140
+ To list the supported shapes, run the following command:
141
+
142
+ ```
143
+ $ aws-edges -S
144
+ ```
145
+
146
+ or
147
+
148
+ ```
149
+ $ aws-edges --list-shapes
150
+ ```
151
+
152
+ To use shapes, in your config simply add either `to_shape: "triangle"` or `from_shape: "egg"` to the `edges` section of the config. (See example above)
153
+
154
+ ## Hacking and Contributing
155
+
156
+ Fork it and start hacking. Below is a brief summary of the layout of the code.
157
+
158
+ Inside 'lib/aws-edges' are the classes used. These are kind of categorized by the AWS Ruby SDK "describe*" methods and client types. For example, *subnet.rb* only parses the EC2 *describe-subnets* output. Each class file contains an *initialize* method and a *supported_fields* public method.
159
+
160
+ The 'config.rb' is used to parse the configuration file (which can be either yaml or json). It uses the *supported_fields* method to validate what is entered in the config. It also contains a *valid_keys*, *valid_sources*, *valid_prefixes*.
161
+
162
+ 'graph.rb' is what creates the graphs.
163
+
File without changes
data/SHA256 ADDED
@@ -0,0 +1 @@
1
+ 9076d60c640f2c8a55ca30f030897529861201eb9454fc8de39c8ef16d2e1281 aws-edges-0.7.gem
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'aws-edges/version'
3
+
4
+ Gem::Specification.new 'aws-edges', AWSEdges::VERSION do |s|
5
+ s.license = 'GPL-3.0'
6
+ s.date = '2016-04-07'
7
+ s.summary = 'Simple AWS architecture charts'
8
+ s.description = 'AWS Edges is used to chart out your AWS environments'
9
+ s.files = `git ls-files`.split("\n") - %w[]
10
+ s.author = 'Rick Briganti'
11
+ s.email = 'jeviolle@newaliases.org'
12
+ s.homepage = 'http://github.com/jeviolle/aws-edges'
13
+ s.add_runtime_dependency 'aws-sdk', '~> 1.34', '>=1.34.0'
14
+ s.add_runtime_dependency 'graph', '~> 2.6', '>=2.6.0'
15
+ s.bindir = 'bin'
16
+ s.executables << 'aws-edges'
17
+ end
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+
4
+ $LOAD_PATH.push(File.absolute_path(File.dirname(__FILE__)) + '/../lib')
5
+
6
+ require 'aws-sdk'
7
+ require 'aws-edges'
8
+ require 'optparse'
9
+ require 'pp'
10
+
11
+ options = {}
12
+ OptionParser.new do |opts|
13
+ opts.on('-c','--config [CONFIG_FILE]', 'Config file') do |v|
14
+ options[:config] = v
15
+ end
16
+ opts.on('-a','--access-key-id [ACCESS_KEY_ID]', 'AWS Access Key ID') do |v|
17
+ options[:access_key_id] = v
18
+ end
19
+ opts.on('-s','--secret-key-id [SECRET_ACCESS_KEY]', 'AWS Secret Access Key') do |v|
20
+ options[:secret_access_key] = v
21
+ end
22
+ opts.on('-r','--region [REGION]', 'AWS Region (ie: us-east-1)') do |v|
23
+ options[:region] = v
24
+ end
25
+ opts.on('-C','--list-colors', 'Prints out a list of supported colors') do |v|
26
+ options[:colors] = v
27
+ end
28
+ opts.on('-S','--list-shapes', 'Prints out a list of supported shapes') do |v|
29
+ options[:shapes] = v
30
+ end
31
+ opts.on('-?','--help','Shows this message') do
32
+ puts opts
33
+ exit 255
34
+ end
35
+ end.parse!
36
+
37
+ class Array
38
+ def longest_word
39
+ group_by(&:size).max.last
40
+ end
41
+ end
42
+
43
+ def display_supported(type)
44
+ display_types = []
45
+
46
+ case type
47
+ when "colors"
48
+ puts
49
+ puts "http://graphviz.org/content/color-names"
50
+ display_types = digraph.class::BOLD_COLORS + digraph.class::LIGHT_COLORS
51
+ when "shapes"
52
+ puts
53
+ puts "http://graphviz.org/content/node-shapes"
54
+ display_types = digraph.class::SHAPES
55
+ end
56
+
57
+ maxlen = display_types.longest_word[0].length
58
+ puts
59
+ puts "Supported #{type.capitalize}"
60
+ puts "----------------"
61
+ display_types.each_with_index do |obj, idx|
62
+ if idx.to_i.even?
63
+ print obj
64
+ (maxlen - obj.length).times{print " "}
65
+ print "\t"
66
+ end
67
+ puts "#{obj}" if idx.to_i.odd?
68
+ end
69
+ puts
70
+ end
71
+
72
+ if options[:colors] and options[:shapes]
73
+ %w/colors shapes/.each{|i| display_supported i}
74
+ exit 255
75
+ elsif options[:colors]
76
+ display_supported "colors"
77
+ exit 255
78
+ elsif options[:shapes]
79
+ display_supported "shapes"
80
+ exit 255
81
+ end
82
+
83
+ unless options[:access_key_id] and options[:secret_access_key] and options[:region] and options[:config]
84
+ puts "[ERROR] Missing required option, please run with '--help' for more info"
85
+ exit 1
86
+ end
87
+
88
+ AWS.config({
89
+ :access_key_id => options[:access_key_id],
90
+ :secret_access_key => options[:secret_access_key],
91
+ :region => options[:region]
92
+ })
93
+
94
+ # parse config
95
+ config = AWSEdges::Config.new
96
+ config_data = config.parse(options[:config])
97
+
98
+ aws_sources = config_data.delete('sources')
99
+ aws_data = {
100
+ :vpc => {}, :subnet => {}, :ec2 => {},
101
+ :rds => {}, :redshift => {}, :iam => {}
102
+ }
103
+ # run aws clients to describe and retrieve data
104
+ aws_data[:iam] = AWSEdges::IAM.new(AWS::IAM::Client.new).nodes if aws_sources.include?("iam")
105
+ aws_data[:vpc] = AWSEdges::VPC.new(AWS::EC2::Client.new.describe_vpcs.data).nodes if aws_sources.include?("vpc")
106
+ aws_data[:subnet] = AWSEdges::Subnet.new(AWS::EC2::Client.new.describe_subnets.data).nodes if aws_sources.include?("subnet")
107
+ aws_data[:ec2] = AWSEdges::EC2.new(AWS::EC2::Client.new.describe_instances.data).nodes if aws_sources.include?("ec2")
108
+ aws_data[:rds] = AWSEdges::RDS.new(AWS::RDS::Client.new.describe_db_instances.data).nodes if aws_sources.include?("rds")
109
+ aws_data[:redshift] = AWSEdges::Redshift.new(AWS::Redshift::Client.new.describe_clusters.data).nodes if aws_sources.include?("redshift")
110
+
111
+ # graph a graph based on the config and inputs
112
+ grapher = AWSEdges::Graph.new(config_data, aws_data)
113
+ grapher.create_graph()
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: "my graph name"
3
+ rotate: true
4
+ sources:
5
+ - "ec2"
6
+ - "subnet"
7
+ edges:
8
+ -
9
+ from: "ec2_hypervisor"
10
+ from_color: "orange"
11
+ from_shape: "invhouse"
12
+ to: "ec2_virtualization_type"
13
+ to_color: "coral"
14
+ to_shape: "diamond"
15
+ -
16
+ from: "ec2_virtualization_type"
17
+ to: "ec2_instance_id"
18
+ -
19
+ from: "ec2_instance_id"
20
+ from_color: "dodgerblue"
21
+ to: "ec2_subnet_id"
22
+ to_color: "darkturquoise"
23
+ -
24
+ from: "subnet_cidr_block"
25
+ to: "subnet_availability_zone"
26
+ to_color: "crimson"
27
+ cluster:
28
+ label: "Subnet Legend"
29
+ edges:
30
+ -
31
+ from: "subnet_subnet_id"
32
+ to: "subnet_cidr_block"
33
+ to_color: "firebrick"
34
+
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: "iam_structure"
3
+ rotate: True
4
+ sources:
5
+ - "iam"
6
+ edges:
7
+ -
8
+ from_color: "lightseagreen"
9
+ from: "iam_user_name"
10
+ to_shape: "note"
11
+ to_color: "peru"
12
+ to: "iam_user_policies-policy_name"
13
+ -
14
+ from_color: "lightseagreen"
15
+ from: "iam_users-user_name"
16
+ to: "iam_group_name"
17
+ -
18
+ from_shape: "folder"
19
+ from_color: "orange"
20
+ from: "iam_group_name"
21
+ to: "iam_group_policies-policy_name"
22
+ to_color: "peru"
23
+ to_shape: "note"
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+ require 'yaml'
3
+ require 'graph'
4
+ require 'aws-edges/vpc.rb'
5
+ require 'aws-edges/subnet.rb'
6
+ require 'aws-edges/iam.rb'
7
+ require 'aws-edges/ec2.rb'
8
+ require 'aws-edges/rds.rb'
9
+ require 'aws-edges/redshift.rb'
10
+ require 'aws-edges/version.rb'
11
+ require 'aws-edges/config.rb'
12
+ require 'aws-edges/graph.rb'
13
+
14
+ ##
15
+ # AWS Edges creates a simple chart of your
16
+ # current AWS environment.
17
+ #
18
+ module AWSEdges
19
+ end
@@ -0,0 +1,166 @@
1
+ module AWSEdges
2
+ class Config
3
+ @@valid_keys = ["name", "sources", "cluster", "label", "edges",
4
+ "from", "from_shape", "from_color",
5
+ "to", "to_shape", "to_color" ,"rotate", "save_as"]
6
+ @@valid_sources = ["VPC","EC2","RDS","Redshift","Subnet","IAM"]
7
+ @@valid_prefixes = @@valid_sources
8
+
9
+ def initialize
10
+ @node_types = @@valid_sources.map{|s|
11
+ eval "AWSEdges::#{s}.supported_fields.map{|f| s.downcase + '_' + f}"
12
+ }.flatten
13
+ end
14
+
15
+ def msg_and_exit(msg)
16
+ puts msg
17
+ exit 1
18
+ end
19
+
20
+ ##
21
+ # Validate the source section of the config file
22
+ #
23
+ def validate_sources(sources)
24
+ msg_and_exit("No sources specified in the config") if sources.nil?
25
+ sources.each do |source|
26
+ unless @@valid_sources.map{|i| i.downcase}.include?(source)
27
+ msg_and_exit("Invalid source detected in config: #{source}")
28
+ end
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Verify that a graph name is specified in the config
34
+ #
35
+ def validate_name(graph_name)
36
+ msg_and_exit("No graph name specified in the config") if graph_name.nil?
37
+ end
38
+
39
+ ##
40
+ # Confirm that from and to edges are specified properly and are
41
+ # not a many-to-many relationship
42
+ def validate_nodes(hashed_data)
43
+ nodes = []
44
+ hashed_data.each do |k,v|
45
+ if k == "edges"
46
+ v.each{|e|
47
+ if e["from"].include?('-') and e["to"].include?('-')
48
+ msg_and_exit("Error: from many to many edges detected in config:\n (#{e['from']}) -> (#{e['to']})")
49
+ end
50
+ nodes.push e["from"]
51
+ nodes.push e["to"]
52
+ }
53
+ end
54
+ nodes.push(validate_nodes(v)) if v.class == Hash
55
+ end
56
+ nodes.flatten.uniq.each do |u|
57
+ unless @node_types.include?(u)
58
+ msg_and_exit("Invalid edge node specified: #{u}")
59
+ end
60
+ end
61
+ end
62
+
63
+ ##
64
+ # Internal method for testing a color
65
+ private def test_color(color)
66
+ colors = digraph.class::BOLD_COLORS + digraph.class::LIGHT_COLORS
67
+ msg_and_exit("Invalid color specified: #{color}") unless colors.include?(color)
68
+ end
69
+
70
+ ##
71
+ # Verify that the colors defined are supported by the
72
+ # underlying Graph gem
73
+ def validate_colors(hashed_data)
74
+ hashed_data.each do |k,v|
75
+ if k == "edges"
76
+ v.each{|e|
77
+ if e["from_color"]
78
+ test_color(e["from_color"])
79
+ end
80
+
81
+ if e["to_color"]
82
+ test_color(e["to_color"])
83
+ end
84
+ }
85
+ end
86
+ end
87
+ end
88
+
89
+ ##
90
+ # Internal method for testing a shape
91
+ private def test_shape(shape)
92
+ shapes = digraph.class::SHAPES
93
+ msg_and_exit("Invalid shape specified: #{shape}") unless shapes.include?(shape)
94
+ end
95
+
96
+ ##
97
+ # Verify that the shapes defined are supported by the
98
+ # underlying Graph gem
99
+ def validate_shapes(hashed_data)
100
+ hashed_data.each do |k,v|
101
+ if k == "edges"
102
+ v.each{|e|
103
+ if e["from_shape"]
104
+ test_shape(e["from_shape"])
105
+ end
106
+
107
+ if e["to_shape"]
108
+ test_shape(e["to_shape"])
109
+ end
110
+ }
111
+ end
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Validate that the 'keys' are valid in the config
117
+ def validate_keys(hashed_data)
118
+ keys = []
119
+ hashed_data.each do |k,v|
120
+ unless k == "edges"
121
+ keys.push(k)
122
+ keys.push(validate_keys(v)) if v.class == Hash
123
+ else
124
+ v.each{|e|
125
+ keys.push(validate_keys(e))
126
+ }
127
+ end
128
+ end
129
+ keys.flatten.uniq.each do |u|
130
+ unless @@valid_keys.include?(u)
131
+ msg_and_exit("Invalid key found in config: #{u}")
132
+ end
133
+ end
134
+ end
135
+
136
+ ##
137
+ # Attempt to parse the json/yaml config and run through
138
+ # validation
139
+ def parse(config_file)
140
+ begin
141
+ contents = File.read(config_file)
142
+ rescue Exception => e
143
+ msg_and_exit("Failed to read config: #{e.message}")
144
+ end
145
+
146
+ begin
147
+ config = YAML.load(contents)
148
+ rescue Exception => e
149
+ config = JSON.parse(contents)
150
+ rescue Exception => e
151
+ msg_and_exit("Failed to parse config: #{e.message}")
152
+ end
153
+
154
+ # run through validation checks
155
+ validate_keys(config)
156
+ validate_colors(config)
157
+ validate_shapes(config)
158
+ validate_sources(config['sources'])
159
+ validate_nodes(config)
160
+ validate_name(config['name'])
161
+
162
+ return config
163
+ end
164
+
165
+ end
166
+ end