aws-inventory 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +661 -0
  6. data/Guardfile +19 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +65 -0
  9. data/Rakefile +6 -0
  10. data/exe/aws-inventory +14 -0
  11. data/inventory.gemspec +37 -0
  12. data/lib/inventory.rb +29 -0
  13. data/lib/inventory/acm.rb +15 -0
  14. data/lib/inventory/aws_services.rb +53 -0
  15. data/lib/inventory/base.rb +74 -0
  16. data/lib/inventory/cfn.rb +50 -0
  17. data/lib/inventory/cli.rb +86 -0
  18. data/lib/inventory/cloudwatch.rb +38 -0
  19. data/lib/inventory/command.rb +25 -0
  20. data/lib/inventory/eb.rb +19 -0
  21. data/lib/inventory/ec2.rb +86 -0
  22. data/lib/inventory/ecs.rb +10 -0
  23. data/lib/inventory/ecs/cluster.rb +20 -0
  24. data/lib/inventory/ecs/service.rb +33 -0
  25. data/lib/inventory/elb.rb +71 -0
  26. data/lib/inventory/help.rb +9 -0
  27. data/lib/inventory/help/acm.md +1 -0
  28. data/lib/inventory/help/cfn.md +1 -0
  29. data/lib/inventory/help/cw.md +1 -0
  30. data/lib/inventory/help/eb.md +1 -0
  31. data/lib/inventory/help/ec2.md +1 -0
  32. data/lib/inventory/help/ecs.md +1 -0
  33. data/lib/inventory/help/elb.md +1 -0
  34. data/lib/inventory/help/iam.md +15 -0
  35. data/lib/inventory/help/keypair.md +1 -0
  36. data/lib/inventory/help/rds.md +5 -0
  37. data/lib/inventory/help/sg.md +1 -0
  38. data/lib/inventory/help/vpc.md +1 -0
  39. data/lib/inventory/iam.rb +13 -0
  40. data/lib/inventory/iam/group.rb +22 -0
  41. data/lib/inventory/iam/shared.rb +62 -0
  42. data/lib/inventory/iam/summary.rb +16 -0
  43. data/lib/inventory/iam/user.rb +21 -0
  44. data/lib/inventory/keypair.rb +25 -0
  45. data/lib/inventory/presenter.rb +20 -0
  46. data/lib/inventory/presenters/base.rb +5 -0
  47. data/lib/inventory/presenters/tab.rb +7 -0
  48. data/lib/inventory/presenters/table.rb +10 -0
  49. data/lib/inventory/rds.rb +11 -0
  50. data/lib/inventory/rds/port.rb +53 -0
  51. data/lib/inventory/rds/shared.rb +43 -0
  52. data/lib/inventory/rds/summary.rb +23 -0
  53. data/lib/inventory/route53.rb +29 -0
  54. data/lib/inventory/security_group.rb +11 -0
  55. data/lib/inventory/security_group/open.rb +76 -0
  56. data/lib/inventory/security_group/shared.rb +14 -0
  57. data/lib/inventory/security_group/summary.rb +17 -0
  58. data/lib/inventory/shared.rb +16 -0
  59. data/lib/inventory/version.rb +3 -0
  60. data/lib/inventory/vpc.rb +55 -0
  61. data/spec/lib/cli_spec.rb +31 -0
  62. data/spec/lib/inventory/base_spec.rb +7 -0
  63. data/spec/lib/inventory/security_group/open_spec.rb +31 -0
  64. data/spec/spec_helper.rb +24 -0
  65. 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
@@ -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,10 @@
1
+ class Inventory::Ecs < Inventory::Base
2
+ autoload :Service, "inventory/ecs/service"
3
+ autoload :Cluster, "inventory/ecs/cluster"
4
+
5
+ # Override report
6
+ def report
7
+ Service.new(@options).report
8
+ Cluster.new(@options).report
9
+ end
10
+ 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,9 @@
1
+ module Inventory::Help
2
+ class << self
3
+ def text(namespaced_command)
4
+ path = namespaced_command.to_s.gsub(':','/')
5
+ path = File.expand_path("../help/#{path}.md", __FILE__)
6
+ IO.read(path) if File.exist?(path)
7
+ end
8
+ end
9
+ 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,5 @@
1
+ Reports the RDS inventory
2
+
3
+ $ inventory rds --report-type summary
4
+
5
+ $ inventory rds --report-type port
@@ -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