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.
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