aws-inventory 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|