aws-inventory 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +661 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +6 -0
- data/exe/aws-inventory +14 -0
- data/inventory.gemspec +37 -0
- data/lib/inventory.rb +29 -0
- data/lib/inventory/acm.rb +15 -0
- data/lib/inventory/aws_services.rb +53 -0
- data/lib/inventory/base.rb +74 -0
- data/lib/inventory/cfn.rb +50 -0
- data/lib/inventory/cli.rb +86 -0
- data/lib/inventory/cloudwatch.rb +38 -0
- data/lib/inventory/command.rb +25 -0
- data/lib/inventory/eb.rb +19 -0
- data/lib/inventory/ec2.rb +86 -0
- data/lib/inventory/ecs.rb +10 -0
- data/lib/inventory/ecs/cluster.rb +20 -0
- data/lib/inventory/ecs/service.rb +33 -0
- data/lib/inventory/elb.rb +71 -0
- data/lib/inventory/help.rb +9 -0
- data/lib/inventory/help/acm.md +1 -0
- data/lib/inventory/help/cfn.md +1 -0
- data/lib/inventory/help/cw.md +1 -0
- data/lib/inventory/help/eb.md +1 -0
- data/lib/inventory/help/ec2.md +1 -0
- data/lib/inventory/help/ecs.md +1 -0
- data/lib/inventory/help/elb.md +1 -0
- data/lib/inventory/help/iam.md +15 -0
- data/lib/inventory/help/keypair.md +1 -0
- data/lib/inventory/help/rds.md +5 -0
- data/lib/inventory/help/sg.md +1 -0
- data/lib/inventory/help/vpc.md +1 -0
- data/lib/inventory/iam.rb +13 -0
- data/lib/inventory/iam/group.rb +22 -0
- data/lib/inventory/iam/shared.rb +62 -0
- data/lib/inventory/iam/summary.rb +16 -0
- data/lib/inventory/iam/user.rb +21 -0
- data/lib/inventory/keypair.rb +25 -0
- data/lib/inventory/presenter.rb +20 -0
- data/lib/inventory/presenters/base.rb +5 -0
- data/lib/inventory/presenters/tab.rb +7 -0
- data/lib/inventory/presenters/table.rb +10 -0
- data/lib/inventory/rds.rb +11 -0
- data/lib/inventory/rds/port.rb +53 -0
- data/lib/inventory/rds/shared.rb +43 -0
- data/lib/inventory/rds/summary.rb +23 -0
- data/lib/inventory/route53.rb +29 -0
- data/lib/inventory/security_group.rb +11 -0
- data/lib/inventory/security_group/open.rb +76 -0
- data/lib/inventory/security_group/shared.rb +14 -0
- data/lib/inventory/security_group/summary.rb +17 -0
- data/lib/inventory/shared.rb +16 -0
- data/lib/inventory/version.rb +3 -0
- data/lib/inventory/vpc.rb +55 -0
- data/spec/lib/cli_spec.rb +31 -0
- data/spec/lib/inventory/base_spec.rb +7 -0
- data/spec/lib/inventory/security_group/open_spec.rb +31 -0
- data/spec/spec_helper.rb +24 -0
- metadata +308 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
class Inventory::Cloudwatch < Inventory::Base
|
4
|
+
include ActionView::Helpers::DateHelper
|
5
|
+
|
6
|
+
def header
|
7
|
+
["Alarm Name", "Threshold"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def data
|
11
|
+
alarms.map do |alarm|
|
12
|
+
[
|
13
|
+
alarm.alarm_name,
|
14
|
+
threshold_desc(alarm)
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def threshold_desc(alarm)
|
20
|
+
a = alarm
|
21
|
+
total_period = a.period * a.evaluation_periods
|
22
|
+
time_in_words = distance_of_time_in_words(total_period)
|
23
|
+
"#{a.metric_name} #{compare_map[a.comparison_operator]} #{a.threshold} for #{a.evaluation_periods} datapoints within #{time_in_words}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def compare_map
|
27
|
+
{
|
28
|
+
"GreaterThanOrEqualToThreshold" => ">=",
|
29
|
+
"GreaterThanThreshold" => ">",
|
30
|
+
"LessThanOrEqualToThreshold" => "<=",
|
31
|
+
"LessThanThreshold" => "<",
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def alarms
|
36
|
+
@alarms ||= cw.describe_alarms.metric_alarms
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
module Inventory
|
4
|
+
class Command < Thor
|
5
|
+
class << self
|
6
|
+
def dispatch(m, args, options, config)
|
7
|
+
# Allow calling for help via:
|
8
|
+
# inventory command help
|
9
|
+
# inventory command -h
|
10
|
+
# inventory command --help
|
11
|
+
# inventory command -D
|
12
|
+
#
|
13
|
+
# as well thor's normal way:
|
14
|
+
#
|
15
|
+
# inventory help command
|
16
|
+
help_flags = Thor::HELP_MAPPINGS + ["help"]
|
17
|
+
if args.length > 1 && !(args & help_flags).empty?
|
18
|
+
args -= help_flags
|
19
|
+
args.insert(-2, "help")
|
20
|
+
end
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/inventory/eb.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class Inventory::Eb < Inventory::Base
|
2
|
+
def header
|
3
|
+
["Environment", "Application", "Solution Stack"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
eb_environments.map do |environment|
|
8
|
+
[
|
9
|
+
environment.environment_name,
|
10
|
+
environment.application_name,
|
11
|
+
environment.solution_stack_name,
|
12
|
+
]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def eb_environments
|
17
|
+
@eb_environments ||= eb.describe_environments.environments
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class Inventory::Ec2 < Inventory::Base
|
2
|
+
def header
|
3
|
+
["Name", "Instance Id", "Instance Type", "Platform", "Security Groups"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
instances.map do |i|
|
8
|
+
name = name_from_tag(i)
|
9
|
+
group_names = security_group_names(i)
|
10
|
+
# cost = cost(i)
|
11
|
+
|
12
|
+
[
|
13
|
+
name,
|
14
|
+
i.instance_id,
|
15
|
+
i.instance_type,
|
16
|
+
# cost,
|
17
|
+
platform(i), # windows or linux
|
18
|
+
group_names,
|
19
|
+
]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def name_from_tag(instance)
|
24
|
+
tags = instance.tags
|
25
|
+
name_tag = tags.find { |t| t.key == "Name" }
|
26
|
+
name_tag ? name_tag.value : "(unnamed)"
|
27
|
+
end
|
28
|
+
|
29
|
+
def security_group_names(instance)
|
30
|
+
instance.security_groups.map {|sg| sg.group_name}.join(', ')
|
31
|
+
end
|
32
|
+
|
33
|
+
def cost(instance)
|
34
|
+
cost_type = COST_MAP[instance.instance_type]
|
35
|
+
if cost_type
|
36
|
+
cost = cost_type[platform(instance)]
|
37
|
+
cost.round(2)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def platform(instance)
|
42
|
+
instance.platform || "linux"
|
43
|
+
end
|
44
|
+
|
45
|
+
# hardcode pricing info until access to pricing api is sorted out
|
46
|
+
# these costs are per month.
|
47
|
+
COST_MAP = {
|
48
|
+
"t2.micro" => {
|
49
|
+
"windows" => 11.826,
|
50
|
+
"linux" => 8.468,
|
51
|
+
},
|
52
|
+
"t2.medium" => {
|
53
|
+
"windows" => 47.012,
|
54
|
+
"linux" => 33.872,
|
55
|
+
},
|
56
|
+
"t2.large" => {
|
57
|
+
"windows" => 88.184,
|
58
|
+
"linux" => 67.744,
|
59
|
+
},
|
60
|
+
"r3.4xlarge" => {
|
61
|
+
"windows" => 1419.12,
|
62
|
+
"linux" => 970.9,
|
63
|
+
},
|
64
|
+
"m4.large" => {
|
65
|
+
"windows" => 140.16,
|
66
|
+
"linux" => 73.0,
|
67
|
+
},
|
68
|
+
"m4.2xlarge" => {
|
69
|
+
"windows" => 560.64,
|
70
|
+
"linux" => 292,
|
71
|
+
},
|
72
|
+
"c3.2xlarge" => {
|
73
|
+
"windows" => 548.96,
|
74
|
+
"linux" => 306.6,
|
75
|
+
},
|
76
|
+
}
|
77
|
+
# Currently dont have access to the pricing api so skipping
|
78
|
+
# def describe_pricing
|
79
|
+
# resp = pricing.describe_services(
|
80
|
+
# format_version: "aws_v1",
|
81
|
+
# max_results: 1,
|
82
|
+
# service_code: "AmazonEC2",
|
83
|
+
# )
|
84
|
+
# pp resp
|
85
|
+
# end
|
86
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Inventory::Ecs::Cluster < Inventory::Base
|
2
|
+
def header
|
3
|
+
["Cluster", "Container Instances", "Running Tasks"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
ecs_clusters.map do |cluster|
|
8
|
+
[
|
9
|
+
cluster.cluster_name,
|
10
|
+
cluster.registered_container_instances_count,
|
11
|
+
cluster.running_tasks_count,
|
12
|
+
]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def ecs_clusters
|
17
|
+
cluster_arns = ecs.list_clusters.cluster_arns
|
18
|
+
@ecs_clusters ||= ecs.describe_clusters(clusters: cluster_arns).clusters
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Inventory::Ecs::Service < Inventory::Base
|
2
|
+
def header
|
3
|
+
["Service", "Cluster", "Running Tasks"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
ecs_services.map do |service|
|
8
|
+
[
|
9
|
+
service.service_name,
|
10
|
+
cluster_name(service.cluster_arn),
|
11
|
+
service.running_count,
|
12
|
+
]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def cluster_name(cluster_arn)
|
17
|
+
resp = ecs.describe_clusters(clusters: [cluster_arn]) # cluster takes name or ARN
|
18
|
+
resp.clusters.first.cluster_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def ecs_services
|
22
|
+
cluster_arns = ecs.list_clusters.cluster_arns
|
23
|
+
@ecs_services ||= cluster_arns.map do |cluster_arn|
|
24
|
+
service_arns = ecs.list_services(cluster: cluster_arn).service_arns
|
25
|
+
resp = ecs.describe_services(services: service_arns, cluster: cluster_arn)
|
26
|
+
resp.services
|
27
|
+
end.flatten
|
28
|
+
|
29
|
+
# pp @ecs_services
|
30
|
+
# @ecs_services
|
31
|
+
# @ecs_services ||= ecs.describe_services.services
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Inventory::Elb < Inventory::Base
|
2
|
+
def header
|
3
|
+
["ELB", "Type", "Security Group", "Open Ports"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
data = []
|
8
|
+
elbs.each do |lb|
|
9
|
+
# lb.security_groups is actualy a list of group_ids
|
10
|
+
lb.security_groups.each do |group_id|
|
11
|
+
security_group_name = security_group_name(group_id) # weird: sometimes sg doesnt exist
|
12
|
+
open_ports = open_ports(group_id)
|
13
|
+
|
14
|
+
data << [
|
15
|
+
lb.load_balancer_name,
|
16
|
+
lb_type(lb),
|
17
|
+
security_group_name,
|
18
|
+
open_ports]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
data
|
23
|
+
end
|
24
|
+
|
25
|
+
def lb_type(lb)
|
26
|
+
lb.respond_to?(:type) ? lb.type : 'classic'
|
27
|
+
end
|
28
|
+
|
29
|
+
def elbs
|
30
|
+
application_load_balancers + classic_load_balancers
|
31
|
+
end
|
32
|
+
|
33
|
+
# override custom sort
|
34
|
+
def sort(data)
|
35
|
+
data
|
36
|
+
end
|
37
|
+
|
38
|
+
def classic_load_balancers
|
39
|
+
@classic_load_balancers ||= elbv1.describe_load_balancers.load_balancer_descriptions
|
40
|
+
end
|
41
|
+
|
42
|
+
def security_group_names(lb)
|
43
|
+
# lb.security_groups is actualy a list of group_ids
|
44
|
+
lb.security_groups.map do |group_id|
|
45
|
+
security_group_name(group_id)
|
46
|
+
end.join(', ')
|
47
|
+
end
|
48
|
+
|
49
|
+
# Somehow sometimes there can be an ELB with a security group that does
|
50
|
+
# not actually exist. In the AWS Console it says:
|
51
|
+
# "There was an error loading the Security Groups."
|
52
|
+
def security_group_name(group_id)
|
53
|
+
security_group = security_groups.find { |sg| sg.group_id == group_id }
|
54
|
+
group_name = security_group ? security_group.group_name : "not found"
|
55
|
+
"#{group_id} (#{group_name})"
|
56
|
+
end
|
57
|
+
|
58
|
+
def application_load_balancers
|
59
|
+
@application_load_balancers ||= elbv2.describe_load_balancers.load_balancers
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns an Array of ports with a cidr of 0.0.0.0/0
|
63
|
+
# Delegates to Inventory::SecurityGroup
|
64
|
+
def open_ports(group_id)
|
65
|
+
sg = security_groups.find { |sg| sg.group_id == group_id }
|
66
|
+
return unless sg
|
67
|
+
|
68
|
+
inventory = Inventory::SecurityGroup::Open.new(@options)
|
69
|
+
inventory.ports_open_to_world(sg)
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the AWS Certificate Manager inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the CloudFormation inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the CloudWatch inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the Elastic Beanstalk inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the EC2 inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the ECS inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the Elastic Load Balancer inventory. Reports both Classic and Application Load Balancers.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Reports the IAM inventory.
|
2
|
+
|
3
|
+
List of groups and users in the groups:
|
4
|
+
|
5
|
+
$ inventory iam --report-type groups # this is the default
|
6
|
+
|
7
|
+
$ inventory iam # same as above
|
8
|
+
|
9
|
+
List of the users and their groups:
|
10
|
+
|
11
|
+
$ inventory iam --report-type users
|
12
|
+
|
13
|
+
Summary of number of groups and users
|
14
|
+
|
15
|
+
$ inventory iam --report-type summary
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the Keypair inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the security groups inventory
|
@@ -0,0 +1 @@
|
|
1
|
+
Reports the VPC inventory
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Inventory::Iam < Inventory::Base
|
2
|
+
autoload :Shared, "inventory/iam/shared"
|
3
|
+
autoload :Summary, "inventory/iam/summary"
|
4
|
+
autoload :User, "inventory/iam/user"
|
5
|
+
autoload :Group, "inventory/iam/group"
|
6
|
+
|
7
|
+
# Default is the groups report because it seems like the most useful report
|
8
|
+
def report
|
9
|
+
Summary.new(@options).report if show(:summary)
|
10
|
+
User.new(@options).report if show(:users)
|
11
|
+
Group.new(@options).report if show(:groups)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Inventory::Iam
|
2
|
+
class Group < Inventory::Base
|
3
|
+
include Shared
|
4
|
+
|
5
|
+
def header
|
6
|
+
["Group Name", "User Count", "User Names"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def data
|
10
|
+
data = [["(groupless)", groupless_users.size, groupless_users.join(', ')]]
|
11
|
+
data += groups.map do |group|
|
12
|
+
group_users = users_in_group(group.group_name)
|
13
|
+
[
|
14
|
+
group.group_name,
|
15
|
+
group_users.size,
|
16
|
+
group_users.join(', ')
|
17
|
+
]
|
18
|
+
end
|
19
|
+
data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Inventory::Iam::Shared
|
2
|
+
def group_names(user)
|
3
|
+
groups = groups_for(user)
|
4
|
+
groups.map(&:group_name)
|
5
|
+
end
|
6
|
+
|
7
|
+
@@groups = {}
|
8
|
+
def groups_for(user)
|
9
|
+
return @@groups[user.user_name] if @@groups[user.user_name]
|
10
|
+
|
11
|
+
@@groups[user.user_name] = iam.list_groups_for_user(user_name: user.user_name).groups
|
12
|
+
end
|
13
|
+
|
14
|
+
# iam.list_groups does not show the users in the groups.
|
15
|
+
# so users_in_groups returns an Array of the user_names in the specified group
|
16
|
+
def users_in_group(group_name)
|
17
|
+
# user_name is a String
|
18
|
+
# group_names is an Array of Strings
|
19
|
+
selected_users = all_users.select do |user_name, group_names|
|
20
|
+
group_names.include?(group_name)
|
21
|
+
end
|
22
|
+
selected_users.map { |a| a[0] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def groupless_users
|
26
|
+
selected_users = all_users.select do |user_name, group_names|
|
27
|
+
group_names.empty?
|
28
|
+
end
|
29
|
+
selected_users.map { |a| a[0] }
|
30
|
+
end
|
31
|
+
|
32
|
+
# {
|
33
|
+
# "tung": ["admin", "developers"],
|
34
|
+
# "vuon": ["admin"],
|
35
|
+
# "bob": ["developers"]
|
36
|
+
# }
|
37
|
+
def all_users
|
38
|
+
@all_users ||= users.inject({}) do |result, user|
|
39
|
+
result[user.user_name] = group_names(user)
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def user_count(group)
|
45
|
+
group_counts[group.group_name]
|
46
|
+
end
|
47
|
+
|
48
|
+
def user_names(group)
|
49
|
+
users.each do |user|
|
50
|
+
result[user.user_name] = group_names(user)
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def users
|
56
|
+
@users ||= iam.list_users.users
|
57
|
+
end
|
58
|
+
|
59
|
+
def groups
|
60
|
+
@groups ||= iam.list_groups.groups
|
61
|
+
end
|
62
|
+
end
|